﻿<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
<!-- saved from url=(0049)http://docs.google.com/Doc?id=dc32cxpz_31ghgkhqf3 -->
<HTML lang=zh-CN><HEAD><TITLE>HowTomcatWorks中文版</TITLE>
<SCRIPT>function b(c){this.t={};this.tick=function(d,e,a){a=a?a:(new Date).getTime();this.t[d]=[a,e]};this.tick("start",null,c)}var f=new b;window.jstiming={Timer:b,load:f};try{window.jstiming.pt=window.gtbExternal&&window.gtbExternal.pageT()||window.external&&window.external.pageT}catch(g){};
</SCRIPT>

<META http-equiv=Content-Type content="text/html; charset=utf-8">
<META content="noarchive>" name=robots>
<STYLE type=text/css>TABLE {
	FONT-SIZE: 1em; BORDER-COLLAPSE: collapse
}
TR {
	TEXT-ALIGN: left
}
DIV {
	MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px
}
ADDRESS {
	MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px
}
OL {
	MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px
}
UL {
	MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px
}
LI {
	MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px
}
OPTION {
	MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px
}
SELECT {
	MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px
}
P {
	MARGIN: 0px
}
PRE {
	MARGIN: 0px; FONT-FAMILY: Courier New
}
BODY {
	PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 10pt; PADDING-BOTTOM: 0px; MARGIN: 6px; COLOR: #000; PADDING-TOP: 0px; FONT-FAMILY: Verdana, sans-serif; BACKGROUND-COLOR: #ffffff
}
IMG {
	moz-force-broken-image-icon: 1
}

@media Screen    
{
HTML.pageview {
	OVERFLOW-Y: scroll; OVERFLOW-X: hidden; BACKGROUND-COLOR: #f3f3f3! important
}
BODY {
	MIN-HEIGHT: 1100px; counter-reset: __goog_page__
}
 HTML BODY {
	HEIGHT: 1100px
}
.pageview BODY {
	BORDER-RIGHT: #bbb 2px solid; PADDING-RIGHT: 50px; BORDER-TOP: #ccc 1px solid; PADDING-LEFT: 50px; PADDING-BOTTOM: 40px; MARGIN: 15px auto 25px; BORDER-LEFT: #ccc 1px solid; WIDTH: 648px! important; PADDING-TOP: 40px; BORDER-BOTTOM: #bbb 2px solid
}
 HTML {
	OVERFLOW-Y: scroll
}
 HTML.pageview BODY {
	OVERFLOW-X: auto
}
HTML #wys_frame:unknown {
	LEFT: 0px; OVERFLOW: hidden; WIDTH: 0px; POSITION: fixed; TOP: 0px; HEIGHT: 0px; content:  
}
.writely-callout-data {
	DISPLAY: inline-block; OVERFLOW: hidden; WIDTH: 0px; HEIGHT: 0px
}
.writely-footnote-marker {
	BACKGROUND-IMAGE: url(images/footnote_doc_icon.gif); VERTICAL-ALIGN: top; OVERFLOW: hidden; WIDTH: 7px; BACKGROUND-REPEAT: no-repeat; HEIGHT: 16px; BACKGROUND-COLOR: transparent; moz-user-select: none
}
.editor .writely-footnote-marker {
	CURSOR: move
}
.writely-footnote-marker-highlight {
	BACKGROUND-POSITION: -15px 0px; moz-user-select: text
}
UNKNOWN {
	BACKGROUND: none transparent scroll repeat 0% 0%
}
.writely-footnote-hide-selection :unknown {
	BACKGROUND: none transparent scroll repeat 0% 0%
}
.writely-footnote-hide-selection:unknown {
	BACKGROUND: none transparent scroll repeat 0% 0%
}
.writely-footnote-hide-selection {
	CURSOR: move
}
.writely-comment-yellow {
	BACKGROUND-COLOR: #ffffd7
}
.writely-comment-orange {
	BACKGROUND-COLOR: #ffe3c0
}
.writely-comment-pink {
	BACKGROUND-COLOR: #ffd7ff
}
.writely-comment-green {
	BACKGROUND-COLOR: #d7ffd7
}
.writely-comment-blue {
	BACKGROUND-COLOR: #d7ffff
}
.writely-comment-purple {
	BACKGROUND-COLOR: #eed7ff
}
UNKNOWN {
	LEFT: -1ex; POSITION: relative
}
#cb-p-tgt {
	PADDING-RIGHT: 0.4em; PADDING-LEFT: 0.4em; FONT-SIZE: 8pt; PADDING-BOTTOM: 0.4em; COLOR: #333; PADDING-TOP: 0.4em; BACKGROUND-COLOR: #ddd
}
#cb-p-tgt-can {
	FONT-WEIGHT: bold; MARGIN-LEFT: 2em; COLOR: #36c; TEXT-DECORATION: underline
}
#cb-p-tgt .spin {
	BACKGROUND: url(//ssl.gstatic.com/docs/clipboard/spin_16o.gif) no-repeat; WIDTH: 16px; HEIGHT: 16px
}
    }
H6 {
	FONT-SIZE: 8pt
}
H5 {
	FONT-SIZE: 8pt
}
H4 {
	FONT-SIZE: 10pt
}
H3 {
	FONT-SIZE: 12pt
}
H2 {
	FONT-SIZE: 14pt
}
H1 {
	FONT-SIZE: 18pt
}
BLOCKQUOTE {
	BORDER-RIGHT: #ddd 1px dashed; PADDING-RIGHT: 10px; BORDER-TOP: #ddd 1px dashed; PADDING-LEFT: 10px; PADDING-BOTTOM: 10px; BORDER-LEFT: #ddd 1px dashed; PADDING-TOP: 10px; BORDER-BOTTOM: #ddd 1px dashed
}
.webkit-indent-blockquote {
	BORDER-TOP-STYLE: none; BORDER-RIGHT-STYLE: none; BORDER-LEFT-STYLE: none; BORDER-BOTTOM-STYLE: none
}
A IMG {
	BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px
}
.pb {
	BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; PAGE-BREAK-AFTER: always; WIDTH: 100%! important; HEIGHT: 1px! important; BORDER-RIGHT-WIDTH: 0px
}
.editor .pb {
	BORDER-TOP: #c0c0c0 1px dashed; BORDER-BOTTOM: #c0c0c0 1px dashed
}
DIV.google_header {
	MARGIN-TOP: 1em; MARGIN-BOTTOM: 1em; POSITION: relative
}
DIV.google_footer {
	MARGIN-TOP: 1em; MARGIN-BOTTOM: 1em; POSITION: relative
}
.editor DIV.writely-toc {
	BORDER-RIGHT: #ccc 1px solid; BORDER-TOP: #ccc 1px solid; BORDER-LEFT: #ccc 1px solid; BORDER-BOTTOM: #ccc 1px solid; BACKGROUND-COLOR: #f3f3f3
}
UNKNOWN {
	PADDING-LEFT: 3em; FONT-WEIGHT: bold
}
OL.writely-toc-subheading {
	PADDING-LEFT: 1em; FONT-WEIGHT: normal
}
 HTML writely-toc OL {
	LIST-STYLE-POSITION: inside
}
.writely-toc-none {
	LIST-STYLE-TYPE: none
}
.writely-toc-decimal {
	LIST-STYLE-TYPE: decimal
}
.writely-toc-upper-alpha {
	LIST-STYLE-TYPE: upper-alpha
}
.writely-toc-lower-alpha {
	LIST-STYLE-TYPE: lower-alpha
}
.writely-toc-upper-roman {
	LIST-STYLE-TYPE: upper-roman
}
.writely-toc-lower-roman {
	LIST-STYLE-TYPE: lower-roman
}
.writely-toc-disc {
	LIST-STYLE-TYPE: disc
}
UNKNOWN {
	LIST-STYLE-TYPE: disc
}
UNKNOWN {
	LIST-STYLE-TYPE: decimal
}
BODY {
	FONT-SIZE: 10pt; LINE-HEIGHT: normal; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
}
.editor A:visited {
	COLOR: #551a8b
}
.editor TABLE.zeroBorder {
	BORDER-RIGHT: gray 1px dotted; BORDER-TOP: gray 1px dotted; BORDER-LEFT: gray 1px dotted; BORDER-BOTTOM: gray 1px dotted
}
.editor TABLE.zeroBorder TD {
	BORDER-RIGHT: gray 1px dotted; BORDER-TOP: gray 1px dotted; BORDER-LEFT: gray 1px dotted; BORDER-BOTTOM: gray 1px dotted
}
.editor TABLE.zeroBorder TH {
	BORDER-RIGHT: gray 1px dotted; BORDER-TOP: gray 1px dotted; BORDER-LEFT: gray 1px dotted; BORDER-BOTTOM: gray 1px dotted
}
.editor DIV.google_header {
	BORDER-RIGHT: #dddddd 2px dashed; BORDER-TOP: #dddddd 2px dashed; MIN-HEIGHT: 2em; BORDER-LEFT: #dddddd 2px dashed; WIDTH: 100%; BORDER-BOTTOM: #dddddd 2px dashed; POSITION: static
}
.editor DIV.google_footer {
	BORDER-RIGHT: #dddddd 2px dashed; BORDER-TOP: #dddddd 2px dashed; MIN-HEIGHT: 2em; BORDER-LEFT: #dddddd 2px dashed; WIDTH: 100%; BORDER-BOTTOM: #dddddd 2px dashed; POSITION: static
}
.editor .misspell {
	BACKGROUND-COLOR: yellow
}
.editor .writely-comment {
	BORDER-RIGHT: #c0c0c0 1px dashed; PADDING-RIGHT: 1px; BORDER-TOP: #c0c0c0 1px dashed; PADDING-LEFT: 1px; FONT-SIZE: 9pt; PADDING-BOTTOM: 1px; BORDER-LEFT: #c0c0c0 1px dashed; LINE-HEIGHT: 1.4; PADDING-TOP: 1px; BORDER-BOTTOM: #c0c0c0 1px dashed
}
</STYLE>

<STYLE>BODY {
	MARGIN: 0px
}
#doc-contents {
	MARGIN: 6px
}
#google-view-footer {
	CLEAR: both; BORDER-TOP: thin solid; PADDING-BOTTOM: 0.3em; PADDING-TOP: 0.3em
}
A.google-small-link:link {
	FONT-SIZE: 11px! important; COLOR: #112abb; FONT-FAMILY: Arial,Sans-serif
}
A.google-small-link:visited {
	FONT-SIZE: 11px! important; COLOR: #112abb; FONT-FAMILY: Arial,Sans-serif
}
BODY {
	DIRECTION: inherit
}
P {
	DIRECTION: inherit
}
DIV {
	DIRECTION: inherit
}
TD {
	DIRECTION: inherit
}

@media Print    
{
#google-view-footer {
	DISPLAY: none
}

}
</STYLE>

<SCRIPT>
function viewOnLoad() {
if (document.location.href.indexOf('spi=1') != -1) {
if (navigator.userAgent.toLowerCase().indexOf('msie') != -1) {
window.print();
} else {
window.setTimeout(window.print, 10);
}
}
if (document.location.href.indexOf('hgd=1') != -1) {
var footer = document.getElementById("google-view-footer");
if (footer) {
footer.style.display = 'none';
}
}
}
</SCRIPT>

<META content="MSHTML 6.00.2900.5880" name=GENERATOR></HEAD>
<BODY 
onload="window.jstiming.load.tick('ol'); window.jstiming.report(window.jstiming.load, null, document.location.protocol == 'https:' ? 'https://gg.google.com/csi' : null);">
<DIV id=doc-contents>&nbsp;&nbsp;&nbsp;&nbsp; 
<H1 id=c3gg style="TEXT-ALIGN: center">How Tomcat Works中文版 </H1>
<H2 id=coes>介绍 </H2>
<H3 id=odgj>概要 </H3>
<DIV id=ovm:>&nbsp;&nbsp;&nbsp; 欢迎阅读《How Tomcat 
Works》这本书。这本书解剖了Tomcat4.1.12和5.0.18版本，解释了它的servlet容器的内部运行机制，那是一个免费的，开源的，最受欢迎的servlet容器，代号为Catalina。Tomcat是一个复杂的系统，由许多不同的组件构成。那些想要学习Tomcat运行机制的朋友大部分知道从何入手。这本书会提供一个蓝图，然后为每一个组件构造一个简化版本，使得可以更加容易的理解这些组件。在这之后才会对真实的组件进行解释。 
</DIV>
<DIV id=pbl5>&nbsp;&nbsp;&nbsp; 
你应该从这份简介开始阅读，因为它解释了这本书的结构，同时给你勾画了这个项目构造的简洁轮廓。“准备前提软件”这一节会给你一些指示，例如你需要下载什么样的软件，如何为你的代码创建目录结构等等。 
</DIV>
<DIV id=h21v4></DIV>
<H3 id=i9c0>本书为谁而作 </H3>
<DIV id=pbl50>&nbsp;&nbsp;&nbsp; 这本书是为任何一个使用Java技术进行工作的人而准备的。 </DIV>
<DIV id=v:vo></DIV>
<DIV id=pbl51>
<UL id=v:vo0>
  <LI 
  id=v:vo1>假如你是一个servlet/jsp程序员或者一个Tomcat用户，而且对一个servlet容器是如何工作这个问题你感兴趣的话，这本书就是为你准备的。 

  <LI id=z..o>假如你想加入Tomcat的开发团队的话，这本书就是为你准备的，因为你首先需要学习那些已存在的代码是如何工作的。 
  <LI 
  id=mlg4>假如你从未涉及web开发，但你对一般意义上的软件开发感兴趣的话，你可以在这本书学到一个像Tomcat一样的大型项目是如何进行设计和开发的。 
  <LI id=oxgu>假如你想配置和自定义Tomcat，你也应该读读这本书。 
  <DIV id=oxgu0></DIV></LI></UL></DIV>&nbsp;&nbsp;&nbsp; 
为了理解书中的讨论，你需要了解Java面向对象编程技术以及servlet编程。假如你对这些不熟悉的话，这里有很多书籍可以参考，包括Budi的《Java for 
the Web with Servlets, JSP, and EJB》。为了让这些材料更容易理解，每一章开始都会有便于理解所讨论主题的必要的背景资料介绍。 
<DIV id=i9c00></DIV>
<H3 id=i9c01>Servlet容器是如何工作的 </H3>&nbsp;&nbsp;&nbsp; 
servlet容器是一个复杂的系统。不过，一个servlet容器要为一个servlet的请求提供服务，基本上有三件事要做: 
<UL id=chpx>
  <LI id=chpx0>
  <DIV 
  id=i9c04>创建一个request对象并填充那些有可能被所引用的servlet使用的信息，如参数、头部、cookies、查询字符串、URI等等。一个request对象是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一个实例。 
  </DIV>
  <LI id=kwsc>
  <DIV 
  id=kwsc0>创建一个response对象，所引用的servlet使用它来给客户端发送响应。一个response对象javax.servlet.ServletResponse或javax.servlet.http.ServletResponse接口的一个实例。 
  </DIV>
  <LI id=lg4r>
  <DIV 
  id=lg4r0>调用servlet的service方法，并传入request和response对象。在这里servlet会从request对象取值，给response写值。 
  </DIV></LI></UL>
<DIV id=pbl52>&nbsp;&nbsp;&nbsp; 当你读这些章节的时候，你将会找到关于catalina servlet容器的详细讨论。 
</DIV>
<H3 id=std_>Catalina架构图 </H3>
<DIV id=g.o:>&nbsp;&nbsp;&nbsp; 
Catalina是一个非常复杂的，并优雅的设计开发出来的软件，同时它也是模块化的。基于“Servlet容器是如何工作的”这一节中提到的任务，你可以把Catalina看成是由两个主要模块所组成的：连接器(connector)和容器(container)。在Figure 
I.1中的架构图，当然是简化了。在稍后的章节里边，你将会一个个的揭开所有更小的组件的神秘面纱。&nbsp; </DIV>
<DIV id=g.o:0><IMG id=m6ov src="HowTomcatWorks中文版.files/dc32cxpz_17cstfxssc_b">
<DIV id=rljw6>&nbsp;&nbsp;&nbsp; 现在重新回到Figure 
I.1，连接器是用来“连接”容器里边的请求的。它的工作是为接收到每一个HTTP请求构造一个request和response对象。然后它把流程传递给容器。容器从连接器接收到requset和response对象之后调用servlet的service方法用于响应。谨记，这个描述仅仅是冰山一角而已。这里容器做了相当多事情。例如，在它调用servlet的service方法之前，它必须加载这个servlet，验证用户(假如需要的话)，更新用户会话等等。一个容器为了处理这个进程使用了很多不同的模块，这也并不奇怪。例如，管理模块是用来处理用户会话，而加载器是用来加载servlet类等等。 
</DIV></DIV>
<H3 id=std_1>Tomcat 4和5 </H3>
<DIV id=std_2>&nbsp;&nbsp;&nbsp; 这本书涵盖了Tomcat4和5.这两者有一些不同之处: 
<UL id=tbsz>
  <LI id=tbsz0>
  <DIV id=l3153>Tomcat 5支持Servlet 2.4和JSP 2.0规范，而Tomcat 4支持Servlet 2.3和JSP 1.2。 
  </DIV>
  <LI id=tbsz1>
  <DIV id=l3156>比起Tomcat 4，Tomcat 5有一些更有效率的默认连接器。 </DIV>
  <LI id=tbsz2>
  <DIV id=l3158>Tomcat 5共享一个后台处理线程，而Tomcat 4的组件都有属于自己的后台处理线程。因此，就这一点而言，Tomcat 
  5消耗较少的资源。 </DIV></LI></UL>
<UL id=t0-d>
  <LI id=t0-d0>
  <DIV id=l31512>Tomcat 5并不需要一个映射组件(mapper component)用于查找子组件，因此简化了代码。 
</DIV></LI></UL></DIV>
<H3 id=qckk>各章概述 </H3>
<DIV id=y4-m>&nbsp;&nbsp;&nbsp; 这本书共20章，其中前面两章作为导言。 
<DIV id=l31517>&nbsp;&nbsp;&nbsp; 
第1章说明一个HTTP服务器是如何工作的，第2章突出介绍了一个简单的servlet容器。接下来的两章关注连接器，第5章到第20章涵盖容器里边的每一个组件。以下是各章节的摘要。 
</DIV>
<DIV id=l31521>&nbsp;&nbsp;&nbsp; <B>注意</B>:对于每个章节，会有一个附带程序，类似于正在被解释的组件。 
</DIV></DIV>
<DIV id=tnxe>&nbsp;&nbsp;&nbsp; 
第1章从这本书一开始就介绍了一个简单的HTTP服务器。要建立一个可工作的HTTP服务器，你需要知道在java.net包里边的2个类的内部运作：Socket和ServerSocket。这里有关于这2个类足够的背景资料，使得你能够理解附带程序是如何工作的。 
</DIV>
<DIV id=amk5>&nbsp;&nbsp;&nbsp; 
第2章说明简单的servlet容器是如何工作的。这一章带有2个servlet容器应用，可以处理静态资源和简单的servlet请求。尤其是你将会学到如何创建request和response对象，然后把它们传递给被请求的servlet的service方法。在servlet容器里边还有一个servlet，你可以从一个web浏览器中调用它。 
</DIV>
<DIV id=oeb2>&nbsp;&nbsp;&nbsp; 第3章介绍了一个简化版本的Tomcat 
4默认连接器。这章里边的程序提供了一个学习工具，用于理解第4章里边的讨论的连接器。 </DIV>
<DIV id=xju->&nbsp;&nbsp;&nbsp; 第4章介绍了Tomcat 
4的默认连接器。这个连接器已经不推荐使用，推荐使用一个更快的连接器，Coyote。不过，默认的连接器更简单，更易于理解。 
<DIV id=hp:c2>&nbsp;&nbsp;&nbsp; 
第5章讨论container模块。container指的是org.apache.catalina.Container接口，有4种类型的container:engine, 
host, context和wrapper。这章提供了两个工作于context和wrapper的程序。 </DIV>
<DIV id=hp:c9>&nbsp;&nbsp;&nbsp; 
第6章解释了Lifecycle接口。这个接口定义了一个Catalina组件的生命周期，并提供了一个优雅的方式，用来把在该组件发生的事件通知其他组件。另外，Lifecycle接口提供了一个优雅的机制，用于在Catalina通过单一的start/stop来启动和停止组件 
</DIV></DIV>
<DIV id=v6cs>&nbsp;&nbsp;&nbsp; 第7章包括日志，该组件是用来记录错误信息和其他信息的。 </DIV>
<DIV id=pqih>&nbsp;&nbsp;&nbsp; 
第8章解释了加载器(loader)。加载器是一个重要的Catalina模块，负责加载servlet和一个web应用所需的其他类。这章还展示了如何实现应用的重新加载。 
</DIV>
<DIV id=pqih0>&nbsp;&nbsp;&nbsp; 
第9章讨论了管理器(manager)。这个组件用来管理会话管理中的会话信息。它解释了各式各样类型的管理器，管理器是如何把会话对象持久化的。在章末，你将会学到如何创建一个的应用，该应用使用StandardManager实例来运行一个使用会话对象进行储值的servlet。 
</DIV>
<DIV id=thw2>&nbsp;&nbsp;&nbsp; 第10章包括web应用程序安全性的限制，用来限制进入某些内容。你将会学习与安全相关的实体，例如 
<DIV 
id=hp:c24>主角(principals)，角色(roles)，登陆配置，认证等等。你也将会写两个程序，它们在StandardContext对象中安装一个身份验证阀(authenticator 
valve)并且使用了基本的认证来对用户进行认证。 </DIV>
<DIV id=hp:c29>&nbsp;&nbsp;&nbsp; 
第11章详细解释了在一个web应用中代表一个servlet的org.apache.catalina.core.StandardWrapper类。特别的是，这章解释了过滤器(filter)和一个servlet的service方法是怎样给调用的。这章的附带程序使用StandardWrapper实例来代表servlet。 
</DIV></DIV>
<DIV id=j75q>&nbsp;&nbsp;&nbsp; 
第12章包括了在一个web应用中代表一个servlet的org.apache.catalina.core.StandardContext类。特别是这章讨论了一个StandardContext对象是如何给配置的，对于每个传入的HTTP请求在它里面会发生什么，是怎样支持自动重新加载的，还有就是，在一个在其相关的组件中执行定期任务的线程中，Tomcat 
5是如何共享的。 </DIV>
<DIV id=og02>&nbsp;&nbsp;&nbsp; 
第13章介绍了另外两个容器：host和engine。你也同样可以找到这两个容器的标准实现:org.apache.catalina.core.StandardHost和org.apache.catalina.core.StandardEngine。 
</DIV>
<DIV id=jjy_>&nbsp;&nbsp;&nbsp; 
第14章提供了服务器和服务组件的部分。服务器为整个servlet容器提供了一个优雅的启动和停止机制，而服务为容器和一个或多个连接器提供了一个支架。这章附带的程序说明了如何使用服务器和服务。 
</DIV>
<DIV id=baim>&nbsp;&nbsp;&nbsp; 
第15章解释了通过Digester来配置web应用。Digester是来源于Apache软件基金会的一个令人振奋的开源项目。对那些尚未初步了解的人，这章通过一节略微介绍了Digester库以及XML文件中如何使用它来把节点转换为Java对象。然后解释了用来配置一个StandardContext实例的ContextConfig对象。 
</DIV>
<DIV id=sb4i>&nbsp;&nbsp;&nbsp; 
第16章解释了shutdown钩子，Tomcat使用它总能获得一个机会用于clean-up，而无论用户是怎样停止它的(即适当的发送一个shutdown命令或者不适当的简单关闭控制台)。 
</DIV>
<DIV id=h3_g>&nbsp;&nbsp;&nbsp; 第17章讨论了通过批处理文件和shell脚本对Tomcat进行启动和停止。 </DIV>
<DIV id=tgqy>&nbsp;&nbsp;&nbsp; 第18章介绍了部署工具(deployer)，这个组件是负责部署和安装web应用的。 </DIV>
<DIV id=kdbx>&nbsp;&nbsp;&nbsp; 
第19章讨论了一个特殊的接口，ContainerServlet，能够让servlet访问Catalina的内部对象。特别是，它讨论了Manager应用，你可以通过它来部署应用程序。 
</DIV>
<DIV id=kdbx0>&nbsp;&nbsp;&nbsp; 
第20章讨论了JMX以及Tomcat是如何通过为其内部对象创建MBeans使得这些对象可管理的。 </DIV>
<H3 id=d0oz>各章的程序 </H3>
<DIV id=d0oz0>&nbsp;&nbsp;&nbsp; 
每一章附带了一个或者多个程序，侧重于Catalina的一个特定的组件。通常你可以找到这些简化版本，无论是正在被解释的组件或者解释如何使用Catalina组件的代码。各章节的程序的所有的类和接口都放在ex[章节号].pyrmont包或者它的子包。例如第1章的程序的类就是放在ex01.pyrmont包中。 
</DIV>
<H3 id=ghq30>准备的前提软件 </H3>
<DIV id=ow4_>&nbsp;&nbsp;&nbsp; 这本书附带的程序运行于J2SE1.4版本。压缩源文件可以从作者的网站<A id=ghq34 
href="http://www.brainysoftware.com/">http://www.brainysoftware.com/</A>中下载。它包括Tomcat 
4.1.12和这本书所使用的程序的源代码。假设你已经安装了J2SE 1.4并且你的path环境变量中已经包括了JDK的安装目录，请按照下列步骤： 
<OL id=a7qa>
  <LI id=a7qa0>
  <DIV 
  id=ghq38>解压缩ZIP文件。所有的解压缩文件将放在一个新的目录howtomcatworks中。howtomcatworks将是你的工作目录。在howtomcatworks目录下面将会有数个子目录，包括lib 
  (包括所有所需的库)，src (包括所有的源文件)，webroot (包括一个HTML文件和三个servlet样本)，和webapps 
  (包括示例应用程序)。 </DIV>
  <LI id=a7qa1>
  <DIV 
  id=ghq314>改变目录到工作目录下并编译java文件。加入你使用的是Windows，运行win-compile.bat文件。假如你的计算机是Linux机器，敲入以下内容：(如有必要的话不用忘记使用chmod更改文件属性) 
  </DIV></LI></OL>
<DIV id=ghq317>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ./linux-compile.sh 
</DIV>
<DIV id=n.59>&nbsp;&nbsp;&nbsp; <B>注意</B>：你可以在ZIP文件中的Readme.txt文件找到更多信息。 
</DIV></DIV>
<H2 id=c86l0>第一章:一个简单的Web服务器 </H2>
<DIV id=stxa>&nbsp;&nbsp;&nbsp; 本章说明java 
web服务器是如何工作的。Web服务器也成为超文本传输协议(HTTP)服务器，因为它使用HTTP来跟客户端进行通信的，这通常是个web浏览器。一个基于java的web服务器使用两个重要的类：java.net.Socket和java.net.ServerSocket，并通过HTTP消息进行通信。因此这章就自然是从HTTP和这两个类的讨论开始的。接下去，解释这章附带的一个简单的web服务器。 
</DIV>
<H3 id=stxa0>超文本传输协议(HTTP) </H3>
<DIV id=aq-y>&nbsp;&nbsp;&nbsp; 
HTTP是一种协议，允许web服务器和浏览器通过互联网进行来发送和接受数据。它是一种请求和响应协议。客户端请求一个文件而服务器响应请求。HTTP使用可靠的TCP连接--TCP默认使用80端口。第一个HTTP版是HTTP/0.9，然后被HTTP/1.0所替代。正在取代HTTP/1.0的是当前版本HTTP/1.1，它定义于征求意见文档(RFC) 
2616，可以从<A id=jm6613 
href="http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf">http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf</A>下载。 

<DIV id=jm6614>&nbsp;&nbsp;&nbsp; <B>注意</B>：本节涵盖的HTTP 
1.1只是简略的帮助你理解web服务器应用发送的消息。假如你对更多详细信息感兴趣，请阅读RFC 2616。 </DIV>
<DIV id=jm6617>&nbsp;&nbsp;&nbsp; 
在HTTP中，始终都是客户端通过建立连接和发送一个HTTP请求从而开启一个事务。web服务器不需要联系客户端或者对客户端做一个回调连接。无论是客户端或者服务器都可以提前终止连接。举例来说，当你正在使用一个web浏览器的时候，可以通过点击浏览器上的停止按钮来停止一个文件的下载进程，从而有效的关闭与web服务器的HTTP连接。 
</DIV></DIV>
<H3 id=h30j>HTTP请求 </H3>
<DIV id=yi62>&nbsp;&nbsp;&nbsp; 一个HTTP请求包括三个组成部分： </DIV>
<UL id=yi620>
  <LI id=yi621>方法—统一资源标识符(URI)—协议/版本 
  <LI id=yi622>
  <DIV id=fhs-4>请求的头部 </DIV></LI></UL>
<UL id=yi623>
  <LI id=yi624>
  <DIV id=fhs-6>主体内容 </DIV></LI></UL>
<DIV id=p5uj>&nbsp;&nbsp;&nbsp; 下面是一个HTTP请求的例子： </DIV>
<DIV id=gywv>
<BLOCKQUOTE id=sknk>POST /examples/default.jsp HTTP/1.1<BR id=p5uj0>Accept: 
  text/plain; text/html<BR id=p5uj1>Accept-Language: en-gb<BR 
  id=p5uj2>Connection: Keep-Alive<BR id=p5uj3>Host: localhost<BR 
  id=p5uj4>User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)<BR 
  id=p5uj5>Content-Length: 33<BR id=p5uj6>Content-Type: 
  application/x-www-form-urlencoded<BR id=p5uj7>Accept-Encoding: gzip, 
  deflate<BR id=xw5z><BR id=p5uj8>lastName=Franks&amp;firstName=Michael 
</BLOCKQUOTE></DIV>
<DIV id=ovhe6>&nbsp;&nbsp;&nbsp; 方法—统一资源标识符(URI)—协议/版本出现在请求的第一行。&nbsp; </DIV>
<DIV id=j8t80>
<BLOCKQUOTE id=d6ei>
  <DIV id=gywv0>POST /examples/default.jsp HTTP/1.1 </DIV></BLOCKQUOTE></DIV>
<DIV id=j8t82>&nbsp;&nbsp;&nbsp; 
这里POST是请求方法，/examples/default.jsp是URI，而HTTP/1.1是协议/版本部分。<BR id=uxjh2>&nbsp; 
&nbsp; 每个HTTP请求可以使用HTTP标准里边提到的多种方法之一。HTTP 1.1支持7种类型的请求：GET, POST,<BR 
id=uxjh4>HEAD, OPTIONS, PUT, DELETE和TRACE。GET和POST在互联网应用里边最普遍使用的。<BR 
id=zh1o>&nbsp;&nbsp;&nbsp; 
URI完全指明了一个互联网资源。URI通常是相对服务器的根目录解释的。因此，始终一斜线/开头。统一资源定位器(URL)其实是一种URI(查看<A 
id=uxjh10 
href="http://www.ietf.org/rfc/rfc2396.txt">http://www.ietf.org/rfc/rfc2396.txt</A>)来的。该协议版本代表了正在使用的HTTP协议的版本。<BR 
id=zh1o0>&nbsp;&nbsp;&nbsp; 
请求的头部包含了关于客户端环境和请求的主体内容的有用信息。例如它可能包括浏览器设置的语言，主体内容的长度等等。每个头部通过一个回车换行符(CRLF)来分隔的。<BR 
id=xw5z0>&nbsp;&nbsp;&nbsp; 
对于HTTP请求格式来说，头部和主体内容之间有一个回车换行符(CRLF)是相当重要的。CRLF告诉HTTP服务器主体内容是在什么地方开始的。在一些互联网编程书籍中，CRLF还被认为是HTTP请求的第四部分。<BR 
id=n:dg>&nbsp;&nbsp;&nbsp; 在前面一个HTTP请求中，主体内容只不过是下面一行：<BR id=uxjh21>
<BLOCKQUOTE id=e7wp>lastName=Franks&amp;firstName=Michael<BR 
id=av:w></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 实体内容在一个典型的HTTP请求中可以很容易的变得更长。<BR id=ydan>
<H3 id=ydan0>HTTP响应 </H3>
<DIV id=qxmz>&nbsp;&nbsp;&nbsp; 类似于HTTP请求，一个HTTP响应也包括三个组成部分： </DIV>
<UL id=qxmz0>
  <LI id=qxmz1>方法—统一资源标识符(URI)—协议/版本 
  <LI id=qxmz2>
  <DIV id=qxmz3>响应的头部 </DIV></LI></UL>
<UL id=qxmz4>
  <LI id=qxmz5>
  <DIV id=qxmz6>主体内容 </DIV></LI></UL>
<DIV id=qxmz7>&nbsp;&nbsp;&nbsp; 下面是一个HTTP响应的例子： </DIV>
<BLOCKQUOTE id=dmq0>HTTP/1.1 200 OK<BR id=nziz0>Server: Microsoft-IIS/4.0<BR 
  id=nziz1>Date: Mon, 5 Jan 2004 13:13:33 GMT<BR id=nziz2>Content-Type: 
  text/html<BR id=nziz3>Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT<BR 
  id=nziz4>Content-Length: 112<BR id=dmq00><BR id=nziz5>&lt;html&gt;<BR 
  id=nziz6>&lt;head&gt;<BR id=nziz7>&lt;title&gt;HTTP Response 
  Example&lt;/title&gt;<BR id=nziz8>&lt;/head&gt;<BR id=nziz9>&lt;body&gt;<BR 
  id=nziz10>Welcome to Brainy Software<BR id=nziz11>&lt;/body&gt;<BR 
  id=nziz12>&lt;/html&gt;<BR id=nziz13></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
响应头部的第一行类似于请求头部的第一行。第一行告诉你该协议使用HTTP 1.1，请求成功(200=成功)，表示一切都运行良好。<BR 
id=nziz17>&nbsp;&nbsp;&nbsp; 
响应头部和请求头部类似，也包括很多有用的信息。响应的主体内容是响应本身的HTML内容。头部和主体内容通过CRLF分隔开来。<BR id=dmq01>
<H3 id=f-0p>Socket类 </H3>&nbsp;&nbsp;&nbsp; 
套接字是网络连接的一个端点。套接字使得一个应用可以从网络中读取和写入数据。放在两个不同计算机上的两个应用可以通过连接发送和接受字节流。为了从你的应用发送一条信息到另一个应用，你需要知道另一个应用的IP地址和套接字端口。在Java里边，套接字指的是java.net.Socket类。<BR 
id=f-0p6>&nbsp;&nbsp;&nbsp; 要创建一个套接字，你可以使用Socket类众多构造方法中的一个。其中一个接收主机名称和端口号：<BR 
id=f-0p8>
<BLOCKQUOTE id=funp>public Socket (java.lang.String host, int port)<BR 
  id=funp0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在这里主机是指远程机器名称或者IP地址，端口是指远程应用的端口号。例如，要连接yahoo.com的80端口，你需要构造以下的Socket对象：<BR 
id=funp3>
<BLOCKQUOTE id=pspg>new Socket ("yahoo.com", 80);<BR 
id=pspg0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
一旦你成功创建了一个Socket类的实例，你可以使用它来发送和接受字节流。要发送字节流，你首先必须调用Socket类的getOutputStream方法来获取一个java.io.OutputStream对象。要发送文本到一个远程应用，你经常要从返回的OutputStream对象中构造一个java.io.PrintWriter对象。要从连接的另一端接受字节流，你可以调用Socket类的getInputStream方法用来返回一个java.io.InputStream对象。<BR 
id=pspg7>&nbsp;&nbsp;&nbsp; 
以下的代码片段创建了一个套接字，可以和本地HTTP服务器(127.0.0.1是指本地主机)进行通讯，发送一个HTTP请求，并从服务器接受响应。它创建了一个StringBuffer对象来保存响应并在控制台上打印出来。<BR 
id=wa7i>
<BLOCKQUOTE id=wa7i0>Socket socket = new Socket("127.0.0.1", "8080");<BR 
  id=wa7i1>OutputStream os = socket.getOutputStream();<BR id=wa7i2>boolean 
  autoflush = true;<BR id=wa7i3>PrintWriter out = new PrintWriter(<BR 
  id=wa7i4>socket.getOutputStream(), autoflush);<BR id=wa7i5>BufferedReader in = 
  new BufferedReader(<BR id=wa7i6>new InputStreamReader( socket.getInputstream() 
  ));<BR id=wa7i7>// send an HTTP request to the web server<BR 
  id=wa7i8>out.println("GET /index.jsp HTTP/1.1");<BR 
  id=wa7i9>out.println("Host: localhost:8080");<BR 
  id=wa7i10>out.println("Connection: Close");<BR id=wa7i11>out.println();<BR 
  id=wa7i12>// read the response<BR id=wa7i13>boolean loop = true;<BR 
  id=wa7i14>StringBuffer sb = new StringBuffer(8096);<BR id=wa7i15>while (loop) 
  {<BR id=wa7i16>&nbsp;&nbsp;&nbsp;&nbsp;if ( in.ready() ) {<BR 
  id=wa7i17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int i=0;<BR 
  id=wa7i18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while (i!=-1) {<BR 
  id=wa7i19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i 
  = in.read();<BR 
  id=wa7i20>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sb.append((char) 
  i);<BR id=wa7i21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=wa7i22>&nbsp;&nbsp;&nbsp;&nbsp;loop = false;<BR 
  id=wa7i23>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=wa7i24>&nbsp;&nbsp;&nbsp;&nbsp;Thread.currentThread().sleep(50);<BR 
  id=wa7i25>}<BR id=wa7i26>// display the response to the out console<BR 
  id=wa7i27>System.out.println(sb.toString());<BR id=wa7i28>socket.close();<BR 
  id=wa7i29></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
请注意，为了从web服务器获取适当的响应，你需要发送一个遵守HTTP协议的HTTP请求。假如你已经阅读了前面一节超文本传输协议(HTTP)，你应该能够理解上面代码提到的HTTP请求。<BR 
id=h7732>&nbsp;&nbsp;&nbsp; 
<B>注意</B>：你可以本书附带的com.brainysoftware.pyrmont.util.HttpSniffer类来发送一个HTTP请求并显示响应。要使用这个Java程序，你必须连接到互联网上。虽然它有可能并不会起作用，假如你有设置防火墙的话。<BR 
id=qpcz>
<H3 id=qpcz0>ServerSocket类 </H3>&nbsp;&nbsp;&nbsp; 
Socket类代表一个客户端套接字，即任何时候你想连接到一个远程服务器应用的时候你构造的套接字，现在，假如你想实施一个服务器应用，例如一个HTTP服务器或者FTP服务器，你需要一种不同的做法。这是因为你的服务器必须随时待命，因为它不知道一个客户端应用什么时候会尝试去连接它。为了让你的应用能随时待命，你需要使用java.net.ServerSocket类。这是服务器套接字的实现。<BR 
id=v:a5>&nbsp;&nbsp;&nbsp; 
ServerSocket和Socket不同，服务器套接字的角色是等待来自客户端的连接请求。一旦服务器套接字获得一个连接请求，它创建一个Socket实例来与客户端进行通信。<BR 
id=v:a50>&nbsp;&nbsp;&nbsp; 
要创建一个服务器套接字，你需要使用ServerSocket类提供的四个构造方法中的一个。你需要指定IP地址和服务器套接字将要进行监听的端口号。通常，IP地址将会是127.0.0.1，也就是说，服务器套接字将会监听本地机器。服务器套接字正在监听的IP地址被称为是绑定地址。服务器套接字的另一个重要的属性是backlog，这是服务器套接字开始拒绝传入的请求之前，传入的连接请求的最大队列长度。<BR 
id=qpcz7>&nbsp;&nbsp;&nbsp; 其中一个ServerSocket类的构造方法如下所示:<BR id=qpcz22>
<BLOCKQUOTE id=w43e>public ServerSocket(int port, int backLog, InetAddress 
  bindingAddress);<BR id=w43e0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
对于这个构造方法，绑定地址必须是java.net.InetAddress的一个实例。一种构造InetAddress对象的简单的方法是调用它的静态方法getByName，传入一个包含主机名称的字符串，就像下面的代码一样。<BR 
id=qpcz27>
<BLOCKQUOTE id=w43e1>InetAddress.getByName("127.0.0.1");<BR 
id=w43e2></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
下面一行代码构造了一个监听的本地机器8080端口的ServerSocket，它的backlog为1。<BR id=qpcz30>
<BLOCKQUOTE id=pqi6>new ServerSocket(8080, 1, 
  InetAddress.getByName("127.0.0.1"));<BR id=pqi60></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
一旦你有一个ServerSocket实例，你可以让它在绑定地址和服务器套接字正在监听的端口上等待传入的连接请求。你可以通过调用ServerSocket类的accept方法做到这点。这个方法只会在有连接请求时才会返回，并且返回值是一个Socket类的实例。Socket对象接下去可以发送字节流并从客户端应用中接受字节流，就像前一节"Socket类"解释的那样。实际上，这章附带的程序中，accept方法是唯一用到的方法。<BR 
id=p8y3>
<H3 id=p8y30>应用程序 </H3>&nbsp;&nbsp;&nbsp; 
我们的web服务器应用程序放在ex01.pyrmont包里边，由三个类组成：<BR id=p8y32>
<UL id=p8y33>
  <LI id=p8y34>HttpServer 
  <LI id=p8y35>Request 
  <LI id=p8y36>Response </LI></UL>&nbsp;&nbsp;&nbsp; 
这个应用程序的入口点(静态main方法)可以在HttpServer类里边找到。main方法创建了一个HttpServer的实例并调用了它的await方法。await方法，顾名思义就是在一个指定的端口上等待HTTP请求,处理它们并发送响应返回客户端。它一直等待直至接收到shutdown命令。<BR 
id=rz0_>&nbsp;&nbsp;&nbsp; 
应用程序不能做什么，除了发送静态资源，例如放在一个特定目录的HTML文件和图像文件。它也在控制台上显示传入的HTTP请求的字节流。不过，它不给浏览器发送任何的头部例如日期或者cookies。<BR 
id=rz0_0>&nbsp;&nbsp;&nbsp; 现在我们将在以下各小节中看看这三个类。<BR id=z43.>
<H3 id=z43.0>HttpServer类 </H3>&nbsp;&nbsp;&nbsp; 
HttpServer类代表一个web服务器并展示在Listing 1.1中。请注意，await方法放在Listing 
1.2中，为了节省空间没有重复放在Listing 1.1中。<BR 
id=fkp7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 1.1: 
HttpServer类<BR id=fkp70>
<BLOCKQUOTE id=gygi>package ex01.pyrmont;<BR id=fkp71>import 
  java.net.Socket;<BR id=fkp72>import java.net.ServerSocket;<BR id=fkp73>import 
  java.net.InetAddress;<BR id=fkp74>import java.io.InputStream;<BR 
  id=fkp75>import java.io.OutputStream;<BR id=fkp76>import 
  java.io.IOException;<BR id=fkp77>import java.io.File;<BR id=fkp78>public class 
  HttpServer {<BR id=fkp79>&nbsp;&nbsp;&nbsp;&nbsp;/** WEB_ROOT is the directory 
  where our HTML and other files reside.<BR id=fkp710>&nbsp;&nbsp;&nbsp;&nbsp;* 
  For this package, WEB_ROOT is the "webroot" directory under the<BR 
  id=fkp711>&nbsp;&nbsp;&nbsp;&nbsp;* working directory.<BR 
  id=fkp712>&nbsp;&nbsp;&nbsp;&nbsp;* The working directory is the location in 
  the file system<BR id=fkp713>&nbsp;&nbsp;&nbsp;&nbsp;* from where the java 
  command was invoked.<BR id=fkp714>&nbsp;&nbsp;&nbsp;&nbsp;*/<BR 
  id=fkp715>&nbsp;&nbsp;&nbsp;&nbsp;public static final String WEB_ROOT =<BR 
  id=fkp716>&nbsp;&nbsp;&nbsp;&nbsp;System.getProperty("user.dir") + 
  File.separator + "webroot";<BR id=fkp717>&nbsp;&nbsp;&nbsp;&nbsp;// shutdown 
  command<BR id=fkp718>&nbsp;&nbsp;&nbsp;&nbsp;private static final String 
  SHUTDOWN_COMMAND = "/SHUTDOWN";<BR id=fkp719>&nbsp;&nbsp;&nbsp;&nbsp;// the 
  shutdown command received<BR id=fkp720>&nbsp;&nbsp;&nbsp;&nbsp;private boolean 
  shutdown = false;<BR id=fkp721>&nbsp;&nbsp;&nbsp;&nbsp;public static void 
  main(String[] args) {<BR 
  id=fkp722>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpServer server = 
  new HttpServer();<BR 
  id=fkp723>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;server.await();<BR 
  id=fkp724>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=fkp725>&nbsp;&nbsp;&nbsp;&nbsp;public void await() {<BR 
  id=fkp726>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;...<BR 
  id=fkp727>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=fkp728>}<BR 
id=hjp7></BLOCKQUOTE>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 
1.2: HttpServer类的await方法 
<BLOCKQUOTE id=gygi0>public void await() {<BR 
  id=uqjk0>&nbsp;&nbsp;&nbsp;&nbsp;ServerSocket serverSocket = null;<BR 
  id=uqjk1>&nbsp;&nbsp;&nbsp;&nbsp;int port = 8080;<BR 
  id=uqjk2>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=uqjk3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;serverSocket = new 
  ServerSocket(port, 1,<BR 
  id=uqjk4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InetAddress.getByName("127.0.0.1"));<BR 
  id=uqjk5>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=uqjk6>&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (IOException e) {<BR id=uqjk7>&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR 
  id=uqjk8>&nbsp;&nbsp;&nbsp;&nbsp;System.exit(1);<BR 
  id=uqjk9>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=uqjk10>&nbsp;&nbsp;&nbsp;&nbsp;// 
  Loop waiting for a request<BR id=uqjk11>&nbsp;&nbsp;&nbsp;&nbsp;while 
  (!shutdown) {<BR 
  id=uqjk12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Socket socket = 
  null;<BR id=uqjk13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InputStream 
  input = null;<BR 
  id=uqjk14>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OutputStream output 
  = null;<BR id=uqjk15>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=uqjk16>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket 
  = serverSocket.accept();<BR 
  id=uqjk17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;input 
  = socket.getInputStream();<BR 
  id=uqjk18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output 
  = socket.getOutputStream();<BR 
  id=uqjk19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  create Request object and parse<BR 
  id=uqjk20>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Request 
  request = new Request(input);<BR 
  id=uqjk21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.parse();<BR 
  id=uqjk22>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  create Response object<BR 
  id=uqjk23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Response 
  response = new Response(output);<BR 
  id=uqjk24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setRequest(request);<BR 
  id=uqjk25>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.sendStaticResource();<BR 
  id=uqjk26>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  Close the socket<BR 
  id=uqjk27>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket.close();<BR 
  id=uqjk28>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//check 
  if the previous URI is a shutdown command<BR 
  id=uqjk29>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shutdown 
  = request.getUri().equals(SHUTDOWN_COMMAND);<BR 
  id=uqjk30>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=uqjk31>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) 
  {<BR 
  id=uqjk32>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace 
  ();<BR 
  id=uqjk33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue;<BR 
  id=uqjk34>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=uqjk35>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=uqjk36>}<BR 
id=gygi1></BLOCKQUOTE>&nbsp;&nbsp;&nbsp;&nbsp; 
web服务器能提供公共静态final变量WEB_ROOT所在的目录和它下面所有的子目录下的静态资源。如下所示，WEB_ROOT被初始化：<BR 
id=gygi4>
<BLOCKQUOTE id=v5vw>public static final String WEB_ROOT =<BR 
  id=v5vw0>System.getProperty("user.dir") + File.separator + "webroot";<BR 
  id=v5vw1></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
代码列表包括一个叫webroot的目录，包含了一些你可以用来测试这个应用程序的静态资源。你同样可以在相同的目录下找到几个servlet用于测试下一章的应用程序。为了请求一个静态资源，在你的浏览器的地址栏或者网址框里边敲入以下的URL：<BR 
id=v5vw6>
<BLOCKQUOTE id=laxe>http://machineName:port/staticResource<BR 
id=laxe0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
如果你要从一个不同的机器上发送请求到你的应用程序正在运行的机器上，machineName应该是正在运行应用程序的机器的名称或者IP地址。假如你的浏览器在同一台机器上，你可以使用localhost作为machineName。端口是8080，staticResource是你需要请求的文件的名称，且必须位于WEB_ROOT里边。<BR 
id=d:9d>&nbsp;&nbsp;&nbsp; 
举例来说，假如你正在使用同一台计算机上测试应用程序，并且你想要调用HttpServer对象去发送一个index.html文件，你可以使用一下的URL：<BR 
id=v5vw14>
<BLOCKQUOTE id=laxe1>http://localhost:8080/index.html<BR 
id=laxe2></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
要停止服务器，你可以在web浏览器的地址栏或者网址框里边敲入预定义字符串，就在URL的host:port的后面，发送一个shutdown命令。shutdown命令是在HttpServer类的静态final变量SHUTDOWN里边定义的：<BR 
id=v5vw19>
<BLOCKQUOTE id=laxe3>private static final String SHUTDOWN_COMMAND = 
  "/SHUTDOWN";<BR id=laxe4></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 因此，要停止服务器，使用下面的URL：<BR 
id=laxe5>
<BLOCKQUOTE id=zxr4>http://localhost:8080/SHUTDOWN<BR 
id=zxr40></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 现在我们来看看Listing 1.2印出来的await方法。<BR 
id=zxr41>&nbsp;&nbsp;&nbsp; 
使用方法名await而不是wait是因为wait方法是与线程相关的java.lang.Object类的一个重要方法。<BR 
id=zxr43>&nbsp;&nbsp;&nbsp; await方法首先创建一个ServerSocket实例然后进入一个while循环。<BR 
id=zxr45>
<BLOCKQUOTE id=k68i>serverSocket = new ServerSocket(port, 1,<BR 
  id=q:.l>&nbsp;&nbsp;&nbsp;&nbsp;InetAddress.getByName("127.0.0.1"));<BR 
  id=q:.l0>...<BR id=q:.l1>// Loop waiting for a request<BR id=q:.l2>while 
  (!shutdown) {<BR id=q:.l3>&nbsp;&nbsp;&nbsp;&nbsp;...<BR id=q:.l4>}<BR 
  id=q:.l5></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
while循环里边的代码运行到ServletSocket的accept方法停了下来，只会在8080端口接收到一个HTTP请求的时候才返回：<BR 
id=b-no0>
<BLOCKQUOTE id=b-no1>socket = serverSocket.accept();<BR 
id=b-no2></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接收到请求之后，await方法从accept方法返回的Socket实例中取得java.io.InputStream和java.io.OutputStream对象。<BR 
id=b-no5>
<BLOCKQUOTE id=ahso>input = socket.getInputStream();<BR id=b-no6>output = 
  socket.getOutputStream();<BR id=ahso0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
await方法接下去创建一个ex01.pyrmont.Request对象并且调用它的parse方法去解析HTTP请求的原始数据。 
<BLOCKQUOTE id=ahso1>// create Request object and parse<BR id=b-no10>Request 
  request = new Request(input);<BR id=b-no11>request.parse ();<BR 
id=ahso2></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在这之后，await方法创建一个Response对象，把Request对象设置给它，并调用它的sendStaticResource方法。<BR 
id=b-no14>
<BLOCKQUOTE id=torb>// create Response object<BR id=b-no15>Response response = 
  new Response(output);<BR id=b-no16>response.setRequest(request);<BR 
  id=b-no17>response.sendStaticResource();<BR 
id=torb0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
最后，await关闭套接字并调用Request的getUri来检测HTTP请求的URI是不是一个shutdown命令。假如是的话，shutdown变量将被设置为true且程序会退出while循环。<BR 
id=b-no21>
<BLOCKQUOTE id=h5_4>// Close the socket<BR id=b-no22>socket.close ();<BR 
  id=b-no23>//check if the previous URI is a shutdown command<BR 
  id=b-no24>shutdown = request.getUri().equals(SHUTDOWN_COMMAND);<BR 
id=h5_40></BLOCKQUOTE>
<H3 id=h5_41>Request类<BR id=cbap></H3>&nbsp;&nbsp;&nbsp; 
ex01.pyrmont.Request类代表一个HTTP请求。从负责与客户端通信的Socket中传递过来InputStream对象来构造这个类的一个实例。你调用InputStream对象其中一个read方法来获取HTTP请求的原始数据。<BR 
id=g.2v>&nbsp;&nbsp;&nbsp; Request类显示在Listing 
1.3。Request对象有parse和getUri两个公共方法，分别在Listings 1.4和1.5列出来。<BR 
id=yvmu0>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 1.3: 
Request类<BR id=yvmu1>
<BLOCKQUOTE id=gc5e>package ex01.pyrmont;<BR id=yvmu2>import 
  java.io.InputStream;<BR id=yvmu3>import java.io.IOException;<BR 
  id=yvmu4>public class Request {<BR id=yvmu5>&nbsp;&nbsp;&nbsp;&nbsp;private 
  InputStream input;<BR id=yvmu6>&nbsp;&nbsp;&nbsp;&nbsp;private String uri;<BR 
  id=yvmu7>&nbsp;&nbsp;&nbsp;&nbsp;public Request(InputStream input) {<BR 
  id=yvmu8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.input = 
  input;<BR id=yvmu9>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=yvmu10>&nbsp;&nbsp;&nbsp;&nbsp;public void parse() {<BR 
  id=yvmu11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<BR 
  id=yvmu12>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=yvmu13>&nbsp;&nbsp;&nbsp;&nbsp;private String parseUri(String 
  requestString) {<BR 
  id=yvmu14>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<BR 
  id=yvmu15>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=yvmu16>&nbsp;&nbsp;&nbsp;&nbsp;public String getUri() {<BR 
  id=yvmu17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return uri;<BR 
  id=yvmu18>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=yvmu19>}<BR 
id=gc5e0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 
1.4: Request类的parse方法 
<BLOCKQUOTE id=c0q8>public void parse() {<BR 
  id=fx-l0>&nbsp;&nbsp;&nbsp;&nbsp;// Read a set of characters from the 
  socket<BR id=fx-l1>&nbsp;&nbsp;&nbsp;&nbsp;StringBuffer request = new 
  StringBuffer(2048);<BR id=fx-l2>&nbsp;&nbsp;&nbsp;&nbsp;int i;<BR 
  id=fx-l3>&nbsp;&nbsp;&nbsp;&nbsp;byte[] buffer = new byte[2048];<BR 
  id=fx-l4>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=fx-l5>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = 
  input.read(buffer);<BR id=fx-l6>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=fx-l7>&nbsp;&nbsp;&nbsp;&nbsp;catch (IOException e) {<BR 
  id=fx-l8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR 
  id=fx-l9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = -1;<BR 
  id=fx-l10>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=fx-l11>&nbsp;&nbsp;&nbsp;&nbsp;for 
  (int j=0; j&lt;i; j++) {<BR 
  id=fx-l12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.append((char) 
  buffer[j]);<BR id=fx-l13>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=fx-l14>&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(request.toString());<BR 
  id=fx-l15>&nbsp;&nbsp;&nbsp;&nbsp;uri = parseUri(request.toString());<BR 
  id=fx-l16>}<BR 
id=c0q80></BLOCKQUOTE>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 
1.5: Request类的parseUri方法<BR id=fx-l18>
<BLOCKQUOTE id=c0q81>private String parseUri(String requestString) {<BR 
  id=scqo>&nbsp;&nbsp;&nbsp;&nbsp;int index1, index2;<BR 
  id=scqo0>&nbsp;&nbsp;&nbsp;&nbsp;index1 = requestString.indexOf(' ');<BR 
  id=scqo1>&nbsp;&nbsp;&nbsp;&nbsp;if (index1 != -1) {<BR 
  id=scqo2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;index2 = 
  requestString.indexOf(' ', index1 + 1);<BR 
  id=scqo3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (index2 &gt; 
  index1)<BR id=scqo4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  requestString.substring(index1 + 1, index2);<BR 
  id=scqo5>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=scqo6>&nbsp;&nbsp;&nbsp;&nbsp;return 
  null;<BR id=scqo7>}<BR id=scqo8></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
parse方法解析HTTP请求里边的原始数据。这个方法没有做很多事情。它唯一可用的信息是通过调用HTTP请求的私有方法parseUri获得的URI。parseUri方法在uri变量里边存储URI。公共方法getUri被调用并返回HTTP请求的URI。<BR 
id=y1l_>&nbsp;&nbsp;&nbsp; <B>注意</B>：在第3章和下面各章的附带程序里边，HTTP请求将会对原始数据进行更多的处理。<BR 
id=y1l_0>&nbsp;&nbsp;&nbsp; 
为了理解parse和parseUri方法是怎样工作的，你需要知道上一节“超文本传输协议(HTTP)”讨论的HTTP请求的结构。在这一章中，我们仅仅关注HTTP请求的第一部分，请求行。请求行从一个方法标记开始，接下去是请求的URI和协议版本，最后是用回车换行符(CRLF)结束。请求行里边的元素是通过一个空格来分隔的。例如，使用GET方法来请求index.html文件的请求行如下所示。<BR 
id=ptij14>
<BLOCKQUOTE id=ojka>GET /index.html HTTP/1.1<BR 
id=ojka0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
parse方法从传递给Requst对象的套接字的InputStream中读取整个字节流并在一个缓冲区中存储字节数组。然后它使用缓冲区字节数据的字节来填入一个StringBuffer对象，并且把代表StringBuffer的字符串传递给parseUri方法。<BR 
id=ecga>&nbsp;&nbsp;&nbsp; parse方法列在Listing 1.4。<BR id=ecga0>&nbsp;&nbsp;&nbsp; 
然后parseUri方法从请求行里边获得URI。Listing 
1.5给出了parseUri方法。parseUri方法搜索请求里边的第一个和第二个空格并从中获取URI。<BR id=b-no25>
<H3 id=dk-4>Response类 </H3>&nbsp;&nbsp;&nbsp; 
ex01.pyrmont.Response类代表一个HTTP响应，在Listing 1.6里边给出。<BR 
id=rict1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 1.6: 
Response类<BR id=rict2>
<BLOCKQUOTE id=z3080>package ex01.pyrmont;<BR id=mhxj>import 
  java.io.OutputStream;<BR id=mhxj0>import java.io.IOException;<BR 
  id=mhxj1>import java.io.FileInputStream;<BR id=mhxj2>import java.io.File;<BR 
  id=mhxj3>/*<BR id=mhxj4>HTTP Response = Status-Line<BR id=mhxj5>*(( 
  general-header | response-header | entity-header ) CRLF)<BR id=mhxj6>CRLF<BR 
  id=mhxj7>[ message-body ]<BR id=mhxj8>Status-Line = HTTP-Version SP 
  Status-Code SP Reason-Phrase CRLF<BR id=mhxj9>*/<BR id=mhxj10>public class 
  Response {<BR id=mhxj11>&nbsp;&nbsp;&nbsp;&nbsp;private static final int 
  BUFFER_SIZE = 1024;<BR id=mhxj12>&nbsp;&nbsp;&nbsp;&nbsp;Request request;<BR 
  id=mhxj13>&nbsp;&nbsp;&nbsp;&nbsp;OutputStream output;<BR 
  id=mhxj14>&nbsp;&nbsp;&nbsp;&nbsp;public Response(OutputStream output) {<BR 
  id=mhxj15>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.output = 
  output;<BR id=mhxj16>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj17>&nbsp;&nbsp;&nbsp;&nbsp;public void setRequest(Request request) {<BR 
  id=mhxj18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.request = 
  request;<BR id=mhxj19>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj20>&nbsp;&nbsp;&nbsp;&nbsp;public void sendStaticResource() throws 
  IOException {<BR 
  id=mhxj21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;byte[] bytes = new 
  byte[BUFFER_SIZE];<BR 
  id=mhxj22>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileInputStream fis 
  = null;<BR id=mhxj23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=mhxj24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;File 
  file = new File(HttpServer.WEB_ROOT, request.getUri());<BR 
  id=mhxj25>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (file.exists()) {<BR 
  id=mhxj26>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fis 
  = new FileInputStream(file);<BR 
  id=mhxj27>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int 
  ch = fis.read(bytes, 0, BUFFER_SIZE);<BR 
  id=mhxj28>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while 
  (ch!=-1) {<BR 
  id=mhxj29>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output.write(bytes, 
  0, ch);<BR 
  id=mhxj30>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ch 
  = fis.read(bytes, 0, BUFFER_SIZE);<BR 
  id=mhxj31>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj32>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else 
  {<BR 
  id=mhxj34>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  file not found<BR 
  id=mhxj35>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String 
  errorMessage = "HTTP/1.1 404 File Not Found\r\n" +<BR 
  id=mhxj36>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Content-Type: 
  text/html\r\n" +<BR 
  id=mhxj37>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Content-Length: 
  23\r\n" +<BR 
  id=mhxj38>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"\r\n" 
  +<BR 
  id=mhxj39>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"&lt;h1&gt;File 
  Not Found&lt;/h1&gt;";<BR 
  id=mhxj40>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output.write(errorMessage.getBytes());<BR 
  id=mhxj41>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj42>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj43>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) 
  {<BR 
  id=mhxj44>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  thrown if cannot instantiate a File object<BR 
  id=mhxj45>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString() 
  );<BR id=mhxj46>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj47>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;finally {<BR 
  id=mhxj48>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (fis!=null)<BR 
  id=mhxj49>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fis.close();<BR 
  id=mhxj50>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=mhxj51>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=gnsx>}<BR 
id=mhxj53></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
首先注意到它的构造方法接收一个java.io.OutputStream对象，就像如下所示。<BR id=h.e80>
<BLOCKQUOTE id=h.e81>public Response(OutputStream output) {<BR 
  id=h.e82>&nbsp;&nbsp;&nbsp;&nbsp;this.output = output;<BR id=h.e83>}<BR 
  id=h.e84></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
响应对象是通过传递由套接字获得的OutputStream对象给HttpServer类的await方法来构造的。Response类有两个公共方法：setRequest和sendStaticResource。setRequest方法用来传递一个Request对象给Response对象。<BR 
id=q3.d>&nbsp;&nbsp;&nbsp; 
sendStaticResource方法是用来发送一个静态资源，例如一个HTML文件。它首先通过传递上一级目录的路径和子路径给File累的构造方法来实例化java.io.File类。<BR 
id=h.e812>
<BLOCKQUOTE id=h.e813>File file = new File(HttpServer.WEB_ROOT, 
  request.getUri());<BR id=h.e814></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后它检查该文件是否存在。假如存在的话，通过传递File对象让sendStaticResource构造一个java.io.FileInputStream对象。然后，它调用FileInputStream的read方法并把字节数组写入OutputStream对象。请注意，这种情况下，静态资源是作为原始数据发送给浏览器的。<BR 
id=gb-z>
<BLOCKQUOTE id=gb-z0>if (file.exists()) {<BR 
  id=gb-z1>&nbsp;&nbsp;&nbsp;&nbsp;fis = new FileInputstream(file);<BR 
  id=gb-z2>&nbsp;&nbsp;&nbsp;&nbsp;int ch = fis.read(bytes, 0, BUFFER_SIZE);<BR 
  id=gb-z3>&nbsp;&nbsp;&nbsp;&nbsp;while (ch!=-1) {<BR 
  id=gb-z4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output.write(bytes, 
  0, ch);<BR id=gb-z5>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ch = 
  fis.read(bytes, 0, BUFFER_SIZE);<BR id=gb-z6>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=gb-z7>}<BR id=gb-z8></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
假如文件并不存在，sendStaticResource方法发送一个错误信息到浏览器。<BR id=gb-z10>
<BLOCKQUOTE id=es_3>String errorMessage =<BR 
  id=wrrx>&nbsp;&nbsp;&nbsp;&nbsp;"Content-Type: text/html\r\n" +<BR 
  id=wrrx0>&nbsp;&nbsp;&nbsp;&nbsp;"Content-Length: 23\r\n" +<BR 
  id=wrrx1>&nbsp;&nbsp;&nbsp;&nbsp;"\r\n" +<BR 
  id=wrrx2>&nbsp;&nbsp;&nbsp;&nbsp;"&lt;h1&gt;File Not Found&lt;/h1&gt;";<BR 
  id=wrrx3>output.write(errorMessage.getBytes());<BR id=es_30></BLOCKQUOTE>
<H3 id=es_31>运行应用程序 </H3>&nbsp;&nbsp;&nbsp; 为了运行应用程序，可以在工作目录下敲入下面的命令：<BR 
id=es_32>
<BLOCKQUOTE id=wa6t>java ex01.pyrmont.HttpServer<BR 
id=es_33></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 为了测试应用程序，可以打开你的浏览器并在地址栏或网址框中敲入下面的命令：<BR 
id=es_35>
<BLOCKQUOTE id=wa6t0>http://localhost:8080/index.html<BR 
id=es_36></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 正如Figure 
1.1所示，你将会在你的浏览器里边看到index.html页面。<BR id=ny4e>
<DIV id=pusm style="TEXT-ALIGN: left"><IMG id=lgvp 
style="WIDTH: 471px; HEIGHT: 326px" 
src="HowTomcatWorks中文版.files/dc32cxpz_18ghq6vzgs_b"></DIV>Figure 1.1: 
web服务器的输出<BR id=ey.2>&nbsp;&nbsp;&nbsp; 在控制台中，你可以看到类似于下面的HTTP请求：<BR id=ey.20>
<BLOCKQUOTE id=e4u3>GET /index.html HTTP/1.1<BR id=ey.21>Accept: image/gif, 
  image/x-xbitmap, image/jpeg, image/pjpeg,<BR 
  id=ey.22>application/vnd.ms-excel, application/msword, application/vnd.ms-<BR 
  id=ey.23>powerpoint, application/x-shockwave-flash, application/pdf, */*<BR 
  id=ey.24>Accept-Language: en-us<BR id=ey.25>Accept-Encoding: gzip, deflate<BR 
  id=ey.26>User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET 
  CLR<BR id=ey.27>1.1.4322)<BR id=ey.28>Host: localhost:8080<BR 
  id=ey.29>Connection: Keep-Alive<BR id=ey.210><BR id=ey.211>GET 
  /images/logo.gif HTTP/1.1<BR id=ey.212>Accept: */*<BR id=ey.213>Referer: 
  http://localhost:8080/index.html<BR id=ey.214>Accept-Language: en-us<BR 
  id=ey.215>Accept-Encoding: gzip, deflate<BR id=ey.216>User-Agent: Mozilla/4.0 
  (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR<BR id=ey.217>1.1.4322)<BR 
  id=ey.218>Host: localhost:8080<BR id=ey.219>Connection: Keep-Alive<BR 
  id=ey.220></BLOCKQUOTE>
<H3 id=e4u30>总结<BR id=u-du></H3>&nbsp;&nbsp;&nbsp; 
在这章中你已经看到一个简单的web服务器是如何工作的。这章附带的程序仅仅由三个类组成，并不是全功能的。不过，它提供了一个良好的学习工具。下一章将要讨论动态内容的处理过程。<BR 
id=t6-s>
<H2 id=t6-s0>第2章:一个简单的Servlet容器 </H2>
<H3 id=t6-s1>概要 </H3>&nbsp;&nbsp;&nbsp; 
本章通过两个程序来说明你如何开发自己的servlet容器。第一个程序被设计得足够简单使得你能理解一个servlet容器是如何工作的。然后它演变为第二个稍微复杂的servlet容器。<BR 
id=q41v>&nbsp;&nbsp;&nbsp; 
<B>注意</B>：每一个servlet容器的应用程序都是从前一章的应用程序逐渐演变过来的，直至一个全功能的Tomcat 
servlet容器在第17章被建立起来。<BR id=q41v1>&nbsp;&nbsp;&nbsp; 
这两个servlet容器都可以处理简单的servlet和静态资源。你可以使用PrimitiveServlet来测试这个容器。PrimitiveServlet在Listing 
2.1中列出并且它的类文件可以在webroot目录下找到。更复杂的servlet就超过这些容器的能力了，但是你将会在以下各章中学到如何建立更复杂的servlet容器。<BR 
id=t6-s13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 2.1: 
PrimitiveServlet.java<BR id=s-zr>
<BLOCKQUOTE id=u93r>import javax.servlet.*;<BR id=g5of>import 
  java.io.IOException;<BR id=g5of0>import java.io.PrintWriter;<BR 
  id=g5of1>public class PrimitiveServlet implements Servlet {<BR 
  id=g5of2>&nbsp;&nbsp;&nbsp;&nbsp;public void init(ServletConfig config) throws 
  ServletException {<BR 
  id=g5of3>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("init");<BR 
  id=g5of4>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=g5of5>&nbsp;&nbsp;&nbsp;&nbsp;public 
  void service(ServletRequest request, ServletResponse response)<BR 
  id=g5of6>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throws 
  ServletException, IOException {<BR 
  id=g5of7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("from 
  service");<BR 
  id=g5of8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PrintWriter out = 
  response.getWriter();<BR 
  id=g5of9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.println("Hello. 
  Roses are red.");<BR 
  id=g5of10>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.print("Violets 
  are blue.");<BR id=g5of11>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=g5of12>&nbsp;&nbsp;&nbsp;&nbsp;public void destroy() {<BR 
  id=g5of13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("destroy");<BR 
  id=g5of14>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=g5of15>&nbsp;&nbsp;&nbsp;&nbsp;public String getServletInfo() {<BR 
  id=g5of16>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=g5of17>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=g5of18>&nbsp;&nbsp;&nbsp;&nbsp;public ServletConfig getServletConfig() {<BR 
  id=g5of19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=g5of20>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=g5of21>}<BR 
id=g5of22></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
两个应用程序的类都放在ex02.pyrmont包里边。为了理解应用程序是如何工作的，你需要熟悉javax.servlet.Servlet接口。为了给你复习一下，将会在本章的首节讨论这个接口。在这之后，你将会学习一个servlet容器做了什么工作来为一个servlet提供HTTP请求。<BR 
id=g5of27>
<H3 id=v1n4>javax.servlet.Servlet接口 </H3>&nbsp;&nbsp;&nbsp; 
Servlet编程是通过javax.servlet和javax.servlet.http这两个包的类和接口来实现的。其中一个至关重要的就是javax.servlet.Servlet接口了。所有的servlet必须实现实现或者继承实现该接口的类。<BR 
id=zs8l>&nbsp;&nbsp;&nbsp; Servlet接口有五个方法，其用法如下。<BR id=v1n44>
<BLOCKQUOTE id=oa-y>public void init(ServletConfig config) throws 
  ServletException<BR id=v1n45>public void service(ServletRequest request, 
  ServletResponse response)<BR id=v1n46>&nbsp;&nbsp;&nbsp;&nbsp;throws 
  ServletException, java.io.IOException<BR id=v1n47>public void destroy()<BR 
  id=v1n48>public ServletConfig getServletConfig()<BR id=v1n49>public 
  java.lang.String getServletInfo()<BR id=v1n410></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在Servlet的五个方法中，init，service和destroy是servlet的生命周期方法。在servlet类已经初始化之后，init方法将会被servlet容器所调用。servlet容器只调用一次，以此表明servlet已经被加载进服务中。init方法必须在servlet可以接受任何请求之前成功运行完毕。一个servlet程序员可以通过覆盖这个方法来写那些仅仅只要运行一次的初始化代码，例如加载数据库驱动，值初始化等等。在其他情况下，这个方法通常是留空的。<BR 
id=gths6>&nbsp;&nbsp;&nbsp; 
servlet容器为servlet请求调用它的service方法。servlet容器传递一个javax.servlet.ServletRequest对象和javax.servlet.ServletResponse对象。ServletRequest对象包括客户端的HTTP请求信息，而ServletResponse对象封装servlet的响应。在servlet的生命周期中，service方法将会给调用多次。<BR 
id=p:d24>&nbsp;&nbsp;&nbsp; 
当从服务中移除一个servlet实例的时候，servlet容器调用destroy方法。这通常发生在servlet容器正在被关闭或者servlet容器需要一些空闲内存的时候。仅仅在所有servlet线程的service方法已经退出或者超时淘汰的时候，这个方法才被调用。在servlet容器已经调用完destroy方法之后，在同一个servlet里边将不会再调用service方法。destroy方法提供了一个机会来清理任何已经被占用的资源，例如内存，文件句柄和线程，并确保任何持久化状态和servlet的内存当前状态是同步的。<BR 
id=p:d213>&nbsp;&nbsp;&nbsp; Listing 
2.1介绍了一个名为PrimitiveServlet的servlet的代码，是一个非常简单的的servlet，你可以用来测试本章里边的servlet容器应用程序。PrimitiveServlet类实现了javax.servlet.Servlet(所有的servlet都必须这样做)，并为Servlet的这五个方法都提供了实现。PrimitiveServlet做的事情非常简单。在init，service或者destroy中的任何一个方法每次被调用的时候，servlet把方法名写到标准控制台上面去。另外，service方法从ServletResponse对象获得java.io.PrintWriter实例，并发送字符串到浏览器去。<BR 
id=ov6s6>
<H3 id=hr52>应用程序1 </H3>&nbsp;&nbsp;&nbsp; 
现在，让我们从一个servlet容器的角度来研究一下servlet编程。总的来说，一个全功能的servlet容器会为servlet的每个HTTP请求做下面一些工作：<BR 
id=hr522>
<UL id=hr523>
  <LI id=hr524>当第一次调用servlet的时候，加载该servlet类并调用servlet的init方法(仅仅一次)。 </LI></UL>
<UL id=hr526>
  <LI 
  id=hr527>对每次请求，构造一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。 
  </LI></UL>
<UL id=hr529>
  <LI id=hr5210>调用servlet的service方法，同时传递ServletRequest和ServletResponse对象。 
</LI></UL>
<UL id=hr5212>
  <LI id=hr5213>当servlet类被关闭的时候，调用servlet的destroy方法并卸载servlet类。 
</LI></UL>&nbsp;&nbsp;&nbsp; 
本章的第一个servlet容器不是全功能的。因此，她不能运行什么除了非常简单的servlet，而且也不调用servlet的init方法和destroy方法。相反它做了下面的事情：<BR 
id=e-j14>
<UL id=sxhk>
  <LI id=sxhk0>等待HTTP请求。 </LI></UL>
<UL id=sxhk1>
  <LI id=sxhk2>构造一个ServletRequest对象和一个ServletResponse对象。 </LI></UL>
<UL id=sxhk3>
  <LI 
  id=sxhk4>假如该请求需要一个静态资源的话，调用StaticResourceProcessor实例的process方法，同时传递ServletRequest和ServletResponse对象。 
  </LI></UL>
<UL id=jjau5>
  <LI 
  id=jjau6>假如该请求需要一个servlet的话，加载servlet类并调用servlet的service方法，同时传递ServletRequest和ServletResponse对象。 
  </LI></UL>&nbsp;&nbsp;&nbsp; 
<B>注意</B>：在这个servlet容器中，每一次servlet被请求的时候，servlet类都会被加载。<BR 
id=ssm_1>&nbsp;&nbsp;&nbsp; 第一个应用程序由6个类组成：<BR id=ssm_2>
<UL id=rv_n>
  <LI id=rv_n0>HttpServer1 
  <LI id=rv_n1>Request 
  <LI id=rv_n2>Response 
  <LI id=rv_n3>StaticResourceProcessor 
  <LI id=rv_n4>ServletProcessor1 
  <LI id=rv_n5>Constants </LI></UL>&nbsp;&nbsp;&nbsp; Figure 
2.1显示了第一个servlet容器的UML图。<BR id=oec2>
<DIV id=wwvz style="TEXT-ALIGN: left"><IMG id=rm4w 
style="WIDTH: 478px; HEIGHT: 302px" 
src="HowTomcatWorks中文版.files/dc32cxpz_19dv5f68gf_b"></DIV>&nbsp;&nbsp;&nbsp;&nbsp;Figure 
2.1: 第一个servlet容器的UML图<BR id=d_7n>&nbsp;&nbsp;&nbsp; 
这个应用程序的入口点(静态main方法)可以在HttpServer1类里边找到。main方法创建了一个HttpServer1的实例并调用了它的await方法。await方法等待HTTP请求，为每次请求创建一个Request对象和一个Response对象，并把他们分发到一个StaticResourceProcessor实例或者一个ServletProcessor实例中去，这取决于请求一个静态资源还是一个servlet。<BR 
id=lhf->&nbsp;&nbsp;&nbsp; 
Constants类包括涉及其他类的静态final变量WEB_ROOT。WEB_ROOT显示了PrimitiveServlet和这个容器可以提供的静态资源的位置。<BR 
id=lhf-0>&nbsp;&nbsp;&nbsp; 
HttpServer1实例会一直等待HTTP请求，直到接收到一个shutdown的命令。你科研用第1章的做法发送一个shutdown命令。<BR 
id=lhf-1>&nbsp;&nbsp;&nbsp; 应用程序里边的每个类都会在以下各节中进行讨论。<BR id=m.fi>
<H3 id=j1h->HttpServer1类 </H3>&nbsp;&nbsp;&nbsp; 
这个应用程序里边的HttpServer1类类似于第1章里边的简单服务器应用程序的HttpServer类。不过，在这个应用程序里边HttpServer1类可以同时提供静态资源和servlet。要请求一个静态资源，你可以在你的浏览器地址栏或者网址框里边敲入一个URL：<BR 
id=m.fi5>
<BLOCKQUOTE id=j1h-0>http://machineName:port/staticResource<BR 
id=m.fi6></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 就像是在第1章提到的，你可以请求一个静态资源。<BR 
id=m.fi8>&nbsp;&nbsp;&nbsp; 为了请求一个servlet，你可以使用下面的URL：<BR id=m.fi9>
<BLOCKQUOTE id=j1h-1>http://machineName:port/servlet/servletClass<BR 
id=m.fi10></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
因此，假如你在本地请求一个名为PrimitiveServlet的servlet，你在浏览器的地址栏或者网址框中敲入：<BR id=m.fi12>
<BLOCKQUOTE id=pk21>http://localhost:8080/servlet/PrimitiveServlet<BR 
  id=m.fi13></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
servlet容器可以就提供PrimitiveServlet了。不过，假如你调用其他servlet，如ModernServlet，servlet容器将会抛出一个异常。在以下各章中，你将会建立可以处理这两个情况的程序。<BR 
id=m.fi16>&nbsp;&nbsp;&nbsp; HttpServer1类显示在Listing 2.2中。<BR 
id=m.fi17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 2.2: 
HttpServer1类的await方法 
<BLOCKQUOTE id=pk210>package ex02.pyrmont;<BR id=j1h-2>import 
  java.net.Socket;<BR id=j1h-3>import java.net.ServerSocket;<BR id=j1h-4>import 
  java.net.InetAddress;<BR id=j1h-5>import java.io.InputStream;<BR 
  id=j1h-6>import java.io.OutputStream;<BR id=j1h-7>import 
  java.io.IOException;<BR id=j1h-8>public class HttpServer1 {<BR 
  id=j1h-9>&nbsp;&nbsp;&nbsp;&nbsp;/** WEB_ROOT is the directory where our HTML 
  and other files reside.<BR id=j1h-10>&nbsp;&nbsp;&nbsp;&nbsp;* For this 
  package, WEB_ROOT is the "webroot" directory under the<BR 
  id=j1h-11>&nbsp;&nbsp;&nbsp;&nbsp;* working directory.<BR 
  id=j1h-12>&nbsp;&nbsp;&nbsp;&nbsp;* The working directory is the location in 
  the file system<BR id=j1h-13>&nbsp;&nbsp;&nbsp;&nbsp;* from where the java 
  command was invoked.<BR id=j1h-14>&nbsp;&nbsp;&nbsp;&nbsp;*/<BR 
  id=j1h-15>&nbsp;&nbsp;&nbsp;&nbsp;// shutdown command<BR 
  id=j1h-16>&nbsp;&nbsp;&nbsp;&nbsp;private static final String SHUTDOWN_COMMAND 
  = "/SHUTDOWN";<BR id=j1h-17>&nbsp;&nbsp;&nbsp;&nbsp;// the shutdown command 
  received<BR id=j1h-18>&nbsp;&nbsp;&nbsp;&nbsp;private boolean shutdown = 
  false;<BR id=j1h-19>&nbsp;&nbsp;&nbsp;&nbsp;public static void main(String[] 
  args) {<BR 
  id=j1h-20>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpServer1 server = 
  new HttpServer1();<BR 
  id=j1h-21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;server.await();<BR 
  id=j1h-22>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j1h-23>&nbsp;&nbsp;&nbsp;&nbsp;public void await() {<BR 
  id=j1h-24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ServerSocket 
  serverSocket = null;<BR 
  id=j1h-25>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int port = 8080;<BR 
  id=j1h-26>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=j1h-27>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;serverSocket 
  = new ServerSocket(port, 1,<BR 
  id=j1h-28>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InetAddress.getByName("127.0.0.1"));<BR 
  id=j1h-29>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j1h-30>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (IOException 
  e) {<BR 
  id=j1h-31>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR 
  id=j1h-32>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(1);<BR 
  id=j1h-33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j1h-34>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Loop waiting for 
  a request<BR id=j1h-35>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while 
  (!shutdown) {<BR 
  id=j1h-36>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Socket 
  socket = null;<BR 
  id=j1h-37>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InputStream 
  input = null;<BR 
  id=j1h-38>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OutputStream 
  output = null;<BR 
  id=j1h-39>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try 
  {<BR 
  id=j1h-40>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket 
  = serverSocket.accept();<BR 
  id=j1h-41>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;input 
  = socket.getInputstream();<BR 
  id=j1h-42>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output 
  = socket.getOutputStream();<BR 
  id=j1h-43>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  create Request object and parse<BR 
  id=j1h-44>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Request 
  request = new Request(input);<BR 
  id=j1h-45>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.parse();<BR 
  id=j1h-46>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  create Response object<BR 
  id=j1h-47>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Response 
  response = new Response(output);<BR 
  id=j1h-48>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setRequest(request);<BR 
  id=j1h-49><SPAN id=b21l 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  check if this is a request for a servlet or</SPAN><BR id=j1h-50 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l0 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  a static resource</SPAN><BR id=j1h-51 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l1 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  a request for a servlet begins with "/servlet/"</SPAN><BR id=j1h-52 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l2 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (request.getUri().startsWith("/servlet/")) {</SPAN><BR id=j1h-53 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l3 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ServletProcessor1 
  processor = new ServletProcessor1();</SPAN><BR id=j1h-54 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l4 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;processor.process(request, 
  response);</SPAN><BR id=j1h-55 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l5 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=j1h-56 style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b21l6 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else 
  {</SPAN><BR id=j1h-57 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l7 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StaticResoureProcessor 
  processor =</SPAN><BR id=j1h-58 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l8 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new 
  StaticResourceProcessor();</SPAN><BR id=j1h-59 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l9 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;processor.process(request, 
  response);</SPAN><BR id=j1h-60 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b21l10 
  style="COLOR: rgb(0,0,0); BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=j1h-61>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  Close the socket<BR 
  id=j1h-62>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket.close();<BR 
  id=j1h-63>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//check 
  if the previous URI is a shutdown command<BR 
  id=j1h-64>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shutdown 
  = request.getUri().equals(SHUTDOWN_COMMAND);<BR 
  id=j1h-65>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j1h-66>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (Exception e) {<BR 
  id=j1h-67>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR 
  id=j1h-68>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(1);<BR 
  id=j1h-69>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j1h-70>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j1h-71>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=j1h-72>}<BR 
id=j1h-73></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
类的await方法等待HTTP请求直到一个shutdown命令给发出，让你想起第1章的await方法。Listing 
2.2的await方法和第1章的区别是，在Listing 
2.2里边，请求可以分发给一个StaticResourceProcessor或者一个ServletProcessor。假如URI包括字符串/servlet/的话，请求将会转发到后面去。<BR 
id=emt03>&nbsp;&nbsp;&nbsp; 不然的话，请求将会传递给StaticResourceProcessor实例 instance. 
请注意，这部分在Listing 2.2中灰暗显示。<BR id=emt05>
<H3 id=ldta>Request类 </H3>&nbsp;&nbsp;&nbsp; 
servlet的service方法从servlet容器中接收一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。这就是说对于每一个HTTP请求，servlet容器必须构造一个ServletRequest对象和一个ServletResponse对象并把它们传递给正在服务的servlet的service方法。<BR 
id=ldta4>&nbsp;&nbsp;&nbsp; 
ex02.pyrmont.Request类代表一个request对象并被传递给servlet的service方法。就本身而言，它必须实现javax.servlet.ServletRequest接口。这个类必须提供这个接口所有方法的实现。不过，我们想要让它非常简单并且仅仅提供实现其中一些方法，我们在以下各章中再实现全部的方法。要编译Request类，你需要把这些方法的实现留空。假如你看过Listing 
2.3中的Request类，你将会看到那些需要返回一个对象的方法返回了null<BR 
id=ldta13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 2.3: 
Request类<BR id=ldta14>
<BLOCKQUOTE id=sn2s>package ex02.pyrmont;<BR id=rlzk>import 
  java.io.InputStream;<BR id=rlzk1>import java.io.IOException;<BR 
  id=rlzk3>import java.io.BufferedReader;<BR id=rlzk5>import 
  java.io.UnsupportedEncodingException;<BR id=rlzk7>import 
  java.util.Enumeration;<BR id=rlzk9>import java.util.Locale;<BR 
  id=rlzk11>import java.util.Map;<BR id=rlzk13>import 
  javax.servlet.RequestDispatcher;<BR id=rlzk15>import 
  javax.servlet.ServletInputStream;<BR id=rlzk17>import 
  javax.servlet.ServletRequest;<BR id=rlzk19>public class Request implements 
  ServletRequest {<BR id=rlzk20>&nbsp;&nbsp;&nbsp;&nbsp;private InputStream 
  input;<BR id=rlzk21>&nbsp;&nbsp;&nbsp;&nbsp;private String uri;<BR 
  id=rlzk22><SPAN id=b18d 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;public 
  Request(InputStream input){</SPAN><BR id=rlzk23 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d0 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.input 
  = input;</SPAN><BR id=rlzk24 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d1 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=rlzk25 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d2 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;public 
  String getUri() {</SPAN><BR id=rlzk26 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d3 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  uri;</SPAN><BR id=rlzk27 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d4 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=rlzk28 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d5 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;private 
  String parseUri(String requestString) {</SPAN><BR id=rlzk29 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d6 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int 
  index1, index2;</SPAN><BR id=rlzk30 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d7 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;index1 
  = requestString.indexOf(' ');</SPAN><BR id=rlzk31 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d8 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (index1 != -1) {</SPAN><BR id=rlzk32 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d9 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;index2 
  = requestString.indexOf(' ', index1 + 1);</SPAN><BR id=rlzk33 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d10 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (index2 &gt; index1)</SPAN><BR id=rlzk34 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d11 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  requestString.substring(index1 + 1, index2);</SPAN><BR id=rlzk35 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d12 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=rlzk36 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d13 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  null;</SPAN><BR id=rlzk37 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d14 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=rlzk38 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d15 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;public void 
  parse() {</SPAN><BR id=rlzk39 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d16 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  Read a set of characters from the socket</SPAN><BR id=rlzk40 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d17 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StringBuffer 
  request = new StringBuffer(2048);</SPAN><BR id=rlzk41 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d18 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int 
  i;</SPAN><BR id=rlzk42 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d19 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;byte[] 
  buffer = new byte[2048];</SPAN><BR id=rlzk43 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d20 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try 
  {</SPAN><BR id=rlzk44 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d21 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i 
  = input.read(buffer);</SPAN><BR id=rlzk45 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d22 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  }</SPAN><BR id=rlzk46 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d23 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (IOException e) {</SPAN><BR id=rlzk47 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d24 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();</SPAN><BR 
  id=rlzk48 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d25 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i 
  = -1;</SPAN><BR id=rlzk49 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  id=b18d26 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=rlzk50 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d27 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for 
  (int j=0; j&lt;i; j++) {</SPAN><BR id=rlzk51 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d28 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.append((char) 
  buffer(j));</SPAN><BR id=rlzk52 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d29 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=rlzk53 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d30 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(request.toString());</SPAN><BR 
  id=rlzk54 style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d31 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri 
  = parseUri(request.toString());</SPAN><BR id=rlzk55 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN id=b18d32 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN><BR 
  id=rlzk56>&nbsp;&nbsp;&nbsp;&nbsp;/* implementation of ServletRequest */<BR 
  id=rlzk57>&nbsp;&nbsp;&nbsp;&nbsp;public Object getAttribute(String attribute) 
  {<BR id=rlzk58>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk59>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk60>&nbsp;&nbsp;&nbsp;&nbsp;public Enumeration getAttributeNames() {<BR 
  id=rlzk61>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk62>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk63>&nbsp;&nbsp;&nbsp;&nbsp;public String getRealPath(String path) {<BR 
  id=rlzk64>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk65>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk66>&nbsp;&nbsp;&nbsp;&nbsp;public RequestDispatcher 
  getRequestDispatcher(String path) {<BR 
  id=rlzk67>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk68>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk69>&nbsp;&nbsp;&nbsp;&nbsp;public boolean isSecure() {<BR 
  id=rlzk70>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false;<BR 
  id=rlzk71>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk72>&nbsp;&nbsp;&nbsp;&nbsp;public String getCharacterEncoding() {<BR 
  id=rlzk73>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk74>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk75>&nbsp;&nbsp;&nbsp;&nbsp;public int getContentLength() {<BR 
  id=rlzk76>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<BR 
  id=rlzk77>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk78>&nbsp;&nbsp;&nbsp;&nbsp;public String getContentType() {<BR 
  id=rlzk79>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk80>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk81>&nbsp;&nbsp;&nbsp;&nbsp;public ServletInputStream getInputStream() 
  throws IOException {<BR 
  id=rlzk82>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk83>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk84>&nbsp;&nbsp;&nbsp;&nbsp;public Locale getLocale() {<BR 
  id=rlzk85>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk86>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk87>&nbsp;&nbsp;&nbsp;&nbsp;public Enumeration getLocales() {<BR 
  id=rlzk88>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk89>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk90>&nbsp;&nbsp;&nbsp;&nbsp;public String getParameter(String name) {<BR 
  id=rlzk91>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk92>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk93>&nbsp;&nbsp;&nbsp;&nbsp;public Map getParameterMap() {<BR 
  id=rlzk94>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk95>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk96>&nbsp;&nbsp;&nbsp;&nbsp;public Enumeration getParameterNames() {<BR 
  id=rlzk97>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk98>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk99>&nbsp;&nbsp;&nbsp;&nbsp;public String[] getParameterValues(String 
  parameter) {<BR 
  id=rlzk100>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk101>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk102>&nbsp;&nbsp;&nbsp;&nbsp;public String getProtocol() {<BR 
  id=rlzk103>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk104>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk105>&nbsp;&nbsp;&nbsp;&nbsp;public BufferedReader getReader() throws 
  IOException {<BR 
  id=rlzk106>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk107>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk108>&nbsp;&nbsp;&nbsp;&nbsp;public String getRemoteAddr() {<BR 
  id=rlzk109>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk110>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk111>&nbsp;&nbsp;&nbsp;&nbsp;public String getRemoteHost() {<BR 
  id=rlzk112>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk113>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk114>&nbsp;&nbsp;&nbsp;&nbsp;public String getScheme() {<BR 
  id=rlzk115>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk116>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk117>&nbsp;&nbsp;&nbsp;&nbsp;public String getServerName() {<BR 
  id=rlzk118>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=rlzk119>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk120>&nbsp;&nbsp;&nbsp;&nbsp;public int getServerPort() {<BR 
  id=rlzk121>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<BR 
  id=rlzk122>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=rlzk123>&nbsp;&nbsp;&nbsp;&nbsp;public void removeAttribute(String 
  attribute) { }<BR id=rlzk124>&nbsp;&nbsp;&nbsp;&nbsp;public void 
  setAttribute(String key, Object value) { }<BR 
  id=rlzk125>&nbsp;&nbsp;&nbsp;&nbsp;public void setCharacterEncoding(String 
  encoding)<BR id=rlzk126>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throws 
  UnsupportedEncodingException { }<BR id=rlzk127>}<BR 
id=rlzk128></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
另外，Request类仍然有在第1章中讨论的parse和getUri方法。<BR id=rlzk130>
<H3 id=sn2s0>Response类 </H3>&nbsp;&nbsp;&nbsp; 在Listing 
2.4列出的ex02.pyrmont.Response类，实现了javax.servlet.ServletResponse。就本身而言，这个类必须提供接口里边的所有方法的实现。类似于Request类，我们把除了getWriter之外的所有方法的实现留空。<BR 
id=rlzk135>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 2.4: 
Response类 
<BLOCKQUOTE id=p7je>package ex02.pyrmont;<BR id=p7je0>import 
  java.io.OutputStream;<BR id=p7je2>import java.io.IOException;<BR 
  id=moea>import java.io.FileInputStream;<BR id=p7je6>import 
  java.io.FileNotFoundException;<BR id=p7je8>import java.io.File;<BR 
  id=p7je10>import java.io.PrintWriter;<BR id=p7je12>import java.util.Locale;<BR 
  id=p7je13>import javax.servlet.ServletResponse;<BR id=p7je14>import 
  javax.servlet.ServletOutputStream;<BR id=p7je15>public class Response 
  implements ServletResponse {<BR id=p7je16>&nbsp;&nbsp;&nbsp;&nbsp;private 
  static final int BUFFER_SIZE = 1024;<BR 
  id=p7je17>&nbsp;&nbsp;&nbsp;&nbsp;Request request;<BR 
  id=p7je18>&nbsp;&nbsp;&nbsp;&nbsp;OutputStream output;<BR 
  id=p7je19>&nbsp;&nbsp;&nbsp;&nbsp;PrintWriter writer;<BR 
  id=p7je20>&nbsp;&nbsp;&nbsp;&nbsp;public Response(OutputStream output) {<BR 
  id=p7je21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.output = 
  output;<BR id=p7je22>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je23>&nbsp;&nbsp;&nbsp;&nbsp;public void setRequest(Request request) {<BR 
  id=p7je24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.request = 
  request;<BR id=p7je25>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je26>&nbsp;&nbsp;&nbsp;&nbsp;/* This method is used to serve static pages 
  */<BR id=p7je27>&nbsp;&nbsp;&nbsp;&nbsp;public void sendStaticResource() 
  throws IOException {<BR 
  id=p7je28>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;byte[] bytes = new 
  byte[BUFFER_SIZE];<BR 
  id=p7je29>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileInputstream fis 
  = null;<BR id=p7je30>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=p7je31>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/* 
  request.getUri has been replaced by request.getRequestURI */<BR 
  id=p7je32>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;File 
  file = new File(Constants.WEB_ROOT, request.getUri());<BR 
  id=p7je33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fis 
  = new FileInputstream(file);<BR 
  id=p7je34>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*<BR 
  id=p7je35>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HTTP 
  Response = Status-Line<BR 
  id=p7je36>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*(( 
  general-header | response-header | entity-header ) CRLF)<BR 
  id=p7je37>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CRLF<BR 
  id=p7je38>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[ 
  message-body ]<BR 
  id=p7je39>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Status-Line 
  = HTTP-Version SP Status-Code SP Reason-Phrase CRLF<BR 
  id=p7je40>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/<BR 
  id=p7je41>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int 
  ch = fis.read(bytes, 0, BUFFER_SIZE);<BR 
  id=p7je42>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while 
  (ch!=-1) {<BR 
  id=p7je43>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output.write(bytes, 
  0, ch);<BR 
  id=p7je44>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ch 
  = fis.read(bytes, 0, BUFFER_SIZE);<BR 
  id=p7je45>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je46>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je47>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (FileNotFoundException e) {<BR 
  id=p7je48>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String 
  errorMessage = "HTTP/1.1 404 File Not Found\r\n" +<BR 
  id=p7je49>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Content-Type: 
  text/html\r\n" +<BR 
  id=p7je50>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Content-Length: 
  23\r\n" +<BR 
  id=p7je51>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"\r\n" 
  +<BR 
  id=p7je52>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"&lt;h1&gt;File 
  Not Found&lt;/h1&gt;";<BR 
  id=p7je53>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output.write(errorMessage.getBytes());<BR 
  id=p7je54>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je55>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;finally {<BR 
  id=p7je56>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (fis!=null)<BR 
  id=p7je57>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fis.close();<BR 
  id=p7je58>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je59>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=p7je60>&nbsp;&nbsp;&nbsp;&nbsp;/** 
  implementation of ServletResponse */<BR 
  id=p7je61>&nbsp;&nbsp;&nbsp;&nbsp;public void flushBuffer() throws IOException 
  ( }<BR id=p7je62>&nbsp;&nbsp;&nbsp;&nbsp;public int getBufferSize() {<BR 
  id=p7je63>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<BR 
  id=p7je64>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je65>&nbsp;&nbsp;&nbsp;&nbsp;public String getCharacterEncoding() {<BR 
  id=p7je66>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=p7je67>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je68>&nbsp;&nbsp;&nbsp;&nbsp;public Locale getLocale() {<BR 
  id=p7je69>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=p7je70>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je71>&nbsp;&nbsp;&nbsp;&nbsp;public ServletOutputStream getOutputStream() 
  throws IOException {<BR 
  id=p7je72>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return null;<BR 
  id=p7je73>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je74>&nbsp;&nbsp;&nbsp;&nbsp;public PrintWriter getWriter() throws 
  IOException {<BR id=p7je75>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  autoflush is true, println() will flush,<BR 
  id=p7je76>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// but print() will 
  not.<BR id=p7je77>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writer = new 
  PrintWriter(output, true);<BR 
  id=p7je78>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return writer;<BR 
  id=p7je79>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je80>&nbsp;&nbsp;&nbsp;&nbsp;public boolean isCommitted() {<BR 
  id=p7je81>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false;<BR 
  id=p7je82>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=p7je83>&nbsp;&nbsp;&nbsp;&nbsp;public void reset() { }<BR 
  id=p7je84>&nbsp;&nbsp;&nbsp;&nbsp;public void resetBuffer() { }<BR 
  id=p7je85>&nbsp;&nbsp;&nbsp;&nbsp;public void setBufferSize(int size) { }<BR 
  id=p7je86>&nbsp;&nbsp;&nbsp;&nbsp;public void setContentLength(int length) { 
  }<BR id=p7je87>&nbsp;&nbsp;&nbsp;&nbsp;public void setContentType(String type) 
  { }<BR id=p7je88>&nbsp;&nbsp;&nbsp;&nbsp;public void setLocale(Locale locale) 
  { }<BR id=p7je89>}<BR id=p7je90></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在getWriter方法中，PrintWriter类的构造方法的第二个参数是一个布尔值表明是否允许自动刷新。传递true作为第二个参数将会使任何println方法的调用都会刷新输出(output)。不过，print方法不会刷新输出。<BR 
id=p7je94>&nbsp;&nbsp;&nbsp; 
因此，任何print方法的调用都会发生在servlet的service方法的最后一行，输出将不会被发送到浏览器。这个缺点将会在下一个应用程序中修复。<BR 
id=p7je97>&nbsp;&nbsp;&nbsp; Response类还拥有在第1章中谈到的sendStaticResource方法。<BR 
id=p7je98>
<H3 id=n:3p>StaticResourceProcessor类<BR id=nmev></H3>&nbsp;&nbsp;&nbsp; 
ex02.pyrmont.StaticResourceProcessor类用来提供静态资源请求。唯一的方法是process方法。Listing 
2.5给出了StaticResourceProcessor类。<BR 
id=n:3p2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 2.5: 
StaticResourceProcessor类<BR id=n:3p3>
<BLOCKQUOTE id=n:3p4>package ex02.pyrmont;<BR id=n:3p5>import 
  java.io.IOException;<BR id=n:3p6>public class StaticResourceProcessor {<BR 
  id=n:3p7>&nbsp;&nbsp;&nbsp;&nbsp;public void process(Request request, Response 
  response) {<BR id=n:3p8>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=n:3p9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.sendStaticResource();<BR 
  id=n:3p10>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=n:3p11>&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (IOException e) {<BR 
  id=n:3p12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR 
  id=n:3p13>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=n:3p14>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=n:3p15>}<BR id=n:3p16></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
process方法接收两个参数：一个ex02.pyrmont.Request实例和一个ex02.pyrmont.Response实例。这个方法只是简单的呼叫Response对象的sendStaticResource方法。<BR 
id=n:3p19>
<H3 id=x:o1>ServletProcessor1类 </H3>&nbsp;&nbsp;&nbsp; Listing 
2.6中的ex02.pyrmont.ServletProcessor1类用于处理servlet的HTTP请求。<BR 
id=x:o11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 2.6: 
ServletProcessor1类<BR id=x:o12>
<BLOCKQUOTE id=x:o13>package ex02.pyrmont;<BR id=x:o14>import java.net.URL;<BR 
  id=x:o16>import java.net.URLClassLoader;<BR id=x:o18>import 
  java.net.URLStreamHandler;<BR id=x:o110>import java.io.File;<BR 
  id=x:o112>import java.io.IOException;<BR id=x:o114>import 
  javax.servlet.Servlet;<BR id=x:o116>import javax.servlet.ServletRequest;<BR 
  id=x:o118>import javax.servlet.ServletResponse;<BR id=x:o120>public class 
  ServletProcessor1 {<BR id=x:o121>&nbsp;&nbsp;&nbsp;&nbsp;public void 
  process(Request request, Response response) {<BR 
  id=x:o122>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String uri = 
  request.getUri();<BR 
  id=x:o123>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String servletName = 
  uri.substring(uri.lastIndexOf("/") + 1);<BR 
  id=x:o124>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;URLClassLoader 
  loader = null;<BR 
  id=x:o125>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=x:o126>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  create a URLClassLoader<BR 
  id=x:o127>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;URL[] 
  urls = new URL[1];<BR 
  id=x:o128>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;URLStreamHandler 
  streamHandler = null;<BR 
  id=x:o129>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;File 
  classPath = new File(Constants.WEB_ROOT);<BR 
  id=x:o130>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  the forming of repository is taken from the<BR 
  id=x:o131>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  createClassLoader method in<BR 
  id=x:o132>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  org.apache.catalina.startup.ClassLoaderFactory<BR 
  id=x:o133>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String 
  repository =(new URL("file", null, classPath.getCanonicalPath() +<BR 
  id=x:o135>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;File.separator)).toString() 
  ;<BR 
  id=x:o136>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  the code for forming the URL is taken from<BR 
  id=x:o137>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  the addRepository method in<BR 
  id=x:o138>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  org.apache.catalina.loader.StandardClassLoader.<BR 
  id=x:o139>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;urls[0] 
  = new URL(null, repository, streamHandler);<BR 
  id=x:o140>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loader 
  = new URLClassLoader(urls);<BR 
  id=x:o141>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x:o142>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (IOException 
  e) {<BR 
  id=x:o143>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString() 
  );<BR id=x:o144>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x:o145>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class myClass = 
  null;<BR id=x:o146>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=x:o147>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;myClass 
  = loader.loadClass(servletName);<BR 
  id=x:o148>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x:o149>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (ClassNotFoundException e) {<BR 
  id=x:o150>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString());<BR 
  id=x:o151>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x:o152>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Servlet servlet = 
  null;<BR id=x:o153>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=x:o154>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;servlet 
  = (Servlet) myClass.newInstance();<BR 
  id=x:o155>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;servlet.service((ServletRequest) 
  request,<BR 
  id=x:o156>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(ServletResponse) 
  response);<BR id=x:o157>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x:o158>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) 
  {<BR 
  id=x:o159>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString());<BR 
  id=x:o160>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x:o161>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Throwable e) 
  {<BR 
  id=x:o162>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString());<BR 
  id=x:o163>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x:o164>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=x:o165>}<BR 
id=x:o166></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
ServletProcessor1类出奇的简单，仅仅由一个方法组成：process。这个方法接受两个参数：一个<BR 
id=x:o168>javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。该方法从ServletRequest中通过调用getRequestUri方法获得URI：<BR 
id=x:o171>
<BLOCKQUOTE id=wdek>String uri = request.getUri();<BR 
id=wdek0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 请记住URI是以下形式的：<BR id=wdek1>
<BLOCKQUOTE id=wdek2>/servlet/servletName<BR 
id=wdek3></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 在这里servletName是servlet类的名字。<BR 
id=wdek4>&nbsp;&nbsp;&nbsp; 
要加载servlet类，我们需要从URI中知道servlet的名称。我们可以使用process方法的下一行来获得servlet的名字：<BR id=wdek6>
<BLOCKQUOTE id=wdek7>String servletName = uri.substring(uri.lastIndexOf("/") + 
  1);<BR id=wdek8></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接下去，process方法加载servlet。要完成这个，你需要创建一个类加载器并告诉这个类加载器要加载的类的位置。对于这个servlet容器，类加载器直接在Constants指向的目录里边查找。WEB_ROOT就是指向工作目录下面的webroot目录。<BR 
id=wdek13>&nbsp;&nbsp;&nbsp; <B>注意</B>： 类加载器将在第8章详细讨论。<BR 
id=wdek14>&nbsp;&nbsp;&nbsp; 
要加载servlet，你可以使用java.net.URLClassLoader类，它是java.lang.ClassLoader类的一个直接子类。一旦你拥有一个URLClassLoader实例，你使用它的loadClass方法去加载一个servlet类。现在举例说明URLClassLoader类是straightforward直接转发的。这个类有三个构造方法，其中最简单的是：<BR 
id=cp9r3>
<BLOCKQUOTE id=cp9r4>public URLClassLoader(URL[] urls);<BR 
id=cp9r5></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
这里urls是一个java.net.URL的对象数组，这些对象指向了加载类时候查找的位置。任何以/结尾的URL都假设是一个目录。否则，URL会Otherwise, 
the URL假定是一个将被下载并在需要的时候打开的JAR文件。<BR id=cp9r9>&nbsp;&nbsp;&nbsp; 
<B>注意</B>：在一个servlet容器里边，一个类加载器可以找到servlet的地方被称为资源库(repository）。<BR 
id=cp9r11>&nbsp;&nbsp;&nbsp; 
在我们的应用程序里边，类加载器必须查找的地方只有一个，如工作目录下面的webroot目录。因此，我们首先创建一个单个URL组成的数组。URL类提供了一系列的构造方法，所以有很多中构造一个URL对象的方式。对于这个应用程序来说，我们使用Tomcat中的另一个类的相同的构造方法。这个构造方法如下所示。<BR 
id=cp9r17>
<BLOCKQUOTE id=ve72>public URL(URL context, java.lang.String spec, 
  URLStreamHandler hander)<BR id=cp9r18>throws MalformedURLException<BR 
  id=cp9r19></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
你可以使用这个构造方法，并为第二个参数传递一个说明，为第一个和第三个参数都传递null。不过，这里有另外一个接受三个参数的构造方法：<BR id=cp9r22>
<BLOCKQUOTE id=yv5u>public URL(java.lang.String protocol, java.lang.String 
  host,<BR id=yv5u0>&nbsp;&nbsp;&nbsp;&nbsp;java.lang.String file) throws 
  MalformedURLException<BR id=yv5u1></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
因此，假如你使用下面的代码时，编译器将不会知道你指的是那个构造方法：<BR id=yv5u3>
<BLOCKQUOTE id=yv5u4>new URL(null, aString, null);<BR 
id=yv5u5></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 你可以通过告诉编译器第三个参数的类型来避开这个问题，例如。<BR 
id=yv5u7>
<BLOCKQUOTE id=fqtn>URLStreamHandler streamHandler = null;<BR id=fqtn0>new 
  URL(null, aString, streamHandler);<BR id=fqtn1></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
你可以使用下面的代码在组成一个包含资源库(servlet类可以被找到的地方)的字符串，并作为第二个参数，<BR id=fqtn3>
<BLOCKQUOTE id=xnn->String repository = (new URL("file", null,<BR 
  id=fqtn4>classPath.getCanonicalPath() + File.separator)).toString() ;<BR 
  id=fqtn5></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
把所有的片段组合在一起，这就是用来构造适当的URLClassLoader实例的process方法中的一部分: 
<BLOCKQUOTE id=l1tu>// create a URLClassLoader<BR id=fqtn8>URL[] urls = new 
  URL[1];<BR id=fqtn9>URLStreamHandler streamHandler = null;<BR id=fqtn10>File 
  classPath = new File(Constants.WEB_ROOT);<BR id=fqtn11>String repository = 
  (new URL("file", null,<BR id=fqtn12>classPath.getCanonicalPath() + 
  File.separator)).toString() ;<BR id=fqtn13>urls[0] = new URL(null, repository, 
  streamHandler);<BR id=fqtn14>loader = new URLClassLoader(urls);<BR 
id=xnn-0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; <B>注意</B>： 
用来生成资源库的代码是从org.apache.catalina.startup.ClassLoaderFactory的createClassLoader方法来的，而生成URL的代码是从org.apache.catalina.loader.StandardClassLoader的addRepository方法来的。不过，在以下各章之前你不需要担心这些类。<BR 
id=xnn-5>&nbsp;&nbsp;&nbsp; 当有了一个类加载器，你可以使用loadClass方法加载一个servlet：<BR id=l1tu0>
<BLOCKQUOTE id=vix6>Class myClass = null;<BR id=l1tu1>try {<BR 
  id=l1tu2>&nbsp;&nbsp;&nbsp;&nbsp;myClass = loader.loadClass(servletName);<BR 
  id=l1tu3>}<BR id=l1tu4>catch (ClassNotFoundException e) {<BR 
  id=l1tu5>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString());<BR 
  id=l1tu6>}<BR id=l1tu7></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，process方法创建一个servlet类加载器的实例, 把它向下转换(downcast)为javax.servlet.Servlet, 
并调用servlet的service方法：<BR id=l1tu9>
<BLOCKQUOTE id=oe1n>Servlet servlet = null;<BR id=l1tu10>try {<BR 
  id=l1tu11>&nbsp;&nbsp;&nbsp;&nbsp;servlet = (Servlet) 
  myClass.newInstance();<BR 
  id=l1tu12>&nbsp;&nbsp;&nbsp;&nbsp;servlet.service((ServletRequest) 
  request,(ServletResponse) response);<BR id=l1tu14>}<BR id=l1tu15>catch 
  (Exception e) {<BR 
  id=l1tu16>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString());<BR 
  id=l1tu17>}<BR id=l1tu18>catch (Throwable e) {<BR 
  id=l1tu19>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString());<BR 
  id=l1tu20>}<BR id=l1tu21></BLOCKQUOTE>
<H3 id=oe1n0>运行应用程序 </H3>&nbsp;&nbsp;&nbsp; 要在Windows上运行该应用程序，在工作目录下面敲入以下命令：<BR 
id=oe1n2>
<BLOCKQUOTE id=oe1n3>java -classpath ./lib/servlet.jar;./ 
  ex02.pyrmont.HttpServer1<BR id=oe1n4></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在Linux下，你使用一个冒号来分隔两个库：<BR id=oe1n5>
<BLOCKQUOTE id=oe1n6>java -classpath ./lib/servlet.jar:./ 
  ex02.pyrmont.HttpServer1<BR id=oe1n7></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
要测试该应用程序，在浏览器的地址栏或者网址框中敲入：<BR id=oe1n9>
<BLOCKQUOTE id=oe1n10>http://localhost:8080/index.html<BR 
id=oe1n11></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 或者<BR id=oe1n12>
<BLOCKQUOTE id=oe1n13>http://localhost:8080/servlet/PrimitiveServlet<BR 
  id=oe1n14></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
当调用PrimitiveServlet的时候，你将会在你的浏览器看到下面的文本：<BR id=oe1n15>
<BLOCKQUOTE id=kg.n>Hello. Roses are red.<BR 
id=oe1n16></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
请注意，因为只是第一个字符串被刷新到浏览器，所以你不能看到第二个字符串Violets are blue。我们将在第3章修复这个问题。<BR id=oe1n18>
<H3 id=exob>应用程序2 </H3>&nbsp;&nbsp;&nbsp; 
第一个应用程序有一个严重的问题。在ServletProcessor1类的process方法，你向上转换ex02.pyrmont.Request实例为javax.servlet.ServletRequest，并作为第一个参数传递给servlet的service方法。你也向下转换ex02.pyrmont.Response实例为javax.servlet.ServletResponse，并作为第二个参数传递给servlet的service方法。<BR 
id=exob5>
<BLOCKQUOTE id=tffx>try {<BR id=b:gs>&nbsp;&nbsp;&nbsp;&nbsp;servlet = 
  (Servlet) myClass.newInstance();<BR 
  id=exob7>&nbsp;&nbsp;&nbsp;&nbsp;servlet.service((ServletRequest) 
  request,(ServletResponse) response);<BR id=exob9>}<BR 
id=exob10></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
这会危害安全性。知道这个servlet容器的内部运作的Servlet程序员可以分别把ServletRequest和ServletResponse实例向下转换为ex02.pyrmont.Request和ex02.pyrmont.Response，并调用他们的公共方法。拥有一个Request实例，它们就可以调用parse方法。拥有一个Response实例，就可以调用sendStaticResource方法。<BR 
id=q63p>&nbsp;&nbsp;&nbsp; 
你不可以把parse和sendStaticResource方法设置为私有的，因为它们将会被其他的类调用。不过，这两个方法是在个servlet内部是不可见的。其中一个解决办法就是让Request和Response类拥有默认访问修饰，所以它们不能在ex02.pyrmont包的外部使用。不过，这里有一个更优雅的解决办法：通过使用facade类。请看Figure 
2.2中的UML图。<BR 
id=q63p0>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
Figure 2.2: Façade classes<BR id=y.l4>
<DIV id=j799 style="TEXT-ALIGN: left"><IMG id=dpyp 
style="WIDTH: 475px; HEIGHT: 143px" 
src="HowTomcatWorks中文版.files/dc32cxpz_20db59cqf8_b"></DIV>&nbsp;&nbsp;&nbsp; 
在这第二个应用程序中，我们增加了两个façade类: 
RequestFacade和ResponseFacade。RequestFacade实现了ServletRequest接口并通过在构造方法中传递一个引用了ServletRequest对象的Request实例作为参数来实例化。ServletRequest接口中每个方法的实现都调用了Request对象的相应方法。然而ServletRequest对象本身是私有的，并不能在类的外部访问。我们构造了一个RequestFacade对象并把它传递给service方法，而不是向下转换Request对象为ServletRequest对象并传递给service方法。Servlet程序员仍然可以向下转换ServletRequest实例为RequestFacade，不过它们只可以访问ServletRequest接口里边的公共方法。现在parseUri方法就是安全的了。<BR 
id=y.l411>&nbsp;&nbsp;&nbsp; Listing 2.7 显示了一个不完整的RequestFacade类<BR 
id=y.l412>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 2.7: 
RequestFacade类<BR id=y.l413>
<BLOCKQUOTE id=vdz8>package ex02.pyrmont;<BR id=xhky>public class 
  RequestFacade implements ServletRequest {<BR 
  id=xhky0>&nbsp;&nbsp;&nbsp;&nbsp;private ServleLRequest request = null;<BR 
  id=xhky1>&nbsp;&nbsp;&nbsp;&nbsp;public RequestFacade(Request request) {<BR 
  id=xhky2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.request = 
  request;<BR id=xhky3>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=xhky4>&nbsp;&nbsp;&nbsp;&nbsp;/* implementation of the ServletRequest*/<BR 
  id=xhky5>&nbsp;&nbsp;&nbsp;&nbsp;public Object getAttribute(String attribute) 
  {<BR id=xhky6>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  request.getAttribute(attribute);<BR id=xhky7>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=xhky8>&nbsp;&nbsp;&nbsp;&nbsp;public Enumeration getAttributeNames() {<BR 
  id=xhky9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  request.getAttributeNames();<BR id=xhky10>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=xhky11>&nbsp;&nbsp;&nbsp;&nbsp;...<BR id=xhky12>}<BR 
id=xhky13></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
请注意RequestFacade的构造方法。它接受一个Request对象并马上赋值给私有的servletRequest对象。还请注意，RequestFacade类的每个方法调用ServletRequest对象的相应的方法。<BR 
id=jwei>&nbsp;&nbsp;&nbsp; 这同样使用于ResponseFacade类。<BR id=jwei0>&nbsp;&nbsp;&nbsp; 
这里是应用程序2中使用的类：<BR id=xhky19>
<UL id=aala>
  <LI id=aala0>HttpServer2 
  <LI id=aala1>Request 
  <LI id=aala2>Response 
  <LI id=aala3>StaticResourceProcessor 
  <LI id=aala4>ServletProcessor2 
  <LI id=aala5>Constants </LI></UL>&nbsp;&nbsp;&nbsp; 
HttpServer2类类似于HttpServer1，除了它在await方法中使用ServletProcessor2而不是ServletProcessor1：<BR 
id=xhky33>
<BLOCKQUOTE id=ybx.>if (request.getUri().startWith("/servlet/")) {<BR 
  id=kn.f><SPAN id=ybx.0 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp;&nbsp;&nbsp;servletProcessor2 
  processor = new ServletProcessor2();</SPAN><BR 
  id=kn.f0>&nbsp;&nbsp;&nbsp;&nbsp;processor.process(request, response);<BR 
  id=kn.f1>}<BR id=kn.f2>else {<BR id=kn.f3>&nbsp;&nbsp;&nbsp;&nbsp;...<BR 
  id=kn.f4>}<BR id=kn.f5></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
ServletProcessor2类类似于ServletProcessor1，除了process方法中的以下部分：<BR id=ybx.2>
<BLOCKQUOTE id=p01m>Servlet servlet = null;<BR id=ybx.3>RequestFacade 
  requestFacade = new RequestFacade(request);<BR id=ybx.4>ResponseFacade 
  responseFacade = new ResponseFacade(response);<BR id=ybx.5>try {<BR 
  id=ybx.6>&nbsp;&nbsp;&nbsp;&nbsp;servlet = (Servlet) myClass.newInstance();<BR 
  id=ybx.7>&nbsp;&nbsp;&nbsp;&nbsp;servlet.service((ServletRequest) 
  requestFacade,(ServletResponse)responseFacade);<BR id=ybx.9>}<BR 
id=ybx.10></BLOCKQUOTE>
<H3 id=p01m0>运行应用程序<BR id=s2ew></H3>&nbsp;&nbsp;&nbsp; 
要在Windows上运行该应用程序，在工作目录下面敲入以下命令：<BR id=p01m1>
<BLOCKQUOTE id=p01m2>java -classpath ./lib/servlet.jar;./ 
  ex02.pyrmont.HttpServer2<BR id=p01m3></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在Linux下，你使用一个冒号来分隔两个库：<BR id=p01m4>
<BLOCKQUOTE id=p01m5>java -classpath ./lib/servlet.jar:./ 
  ex02.pyrmont.HttpServer2<BR id=p01m6></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
你可以使用与应用程序1一样的地址，并得到相同的结果。<BR id=p01m7>
<H3 id=p01m8>总结 </H3>&nbsp;&nbsp;&nbsp; 
本章讨论了两个简单的可以用来提供静态资源和处理像PrimitiveServlet这么简单的servlet的servlet容器。同样也提供了关于javax.servlet.Servlet接口和相关类型的背景信息。<BR 
id=p01m11>
<H2 id=n1mp>第3章:连接器 </H2>
<H3 id=n1mp0>概要 </H3>
<DIV id=n1mp1>&nbsp;&nbsp;&nbsp; 
在介绍中提到，Catalina中有两个主要的模块：连接器和容器。本章中你将会写一个可以创建更好的请求和响应对象的连接器，用来改进第2章中的程序。一个符合Servlet 
2.3和2.4规范的连接器必须创建javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse，并传递给被调用的servlet的service方法。在第2章 
中，servlet容器只可以运行实现了javax.servlet.Servlet的servlet，并传递 
javax.servlet.ServletRequest和javax.servlet.ServletResponse实例给service方法。因为连接器并不知道servlet的类型(例如它是否实现了javax.servlet.Servlet，继承了javax.servlet.GenericServlet，或者继承了javax.servlet.http.HttpServlet)，所以连接器必须始终提供HttpServletRequest和HttpServletResponse的实例。<BR 
id=n1mp13>&nbsp;&nbsp;&nbsp; 在本章的应用程序中，连接器解析HTTP请求头部并让servlet可以获得头部, cookies, 
参数名/值等等。你将会完善第2章中Response类的getWriter方法，让它能够正确运行。由于这些改进，你将会从 
PrimitiveServlet中获取一个完整的响应，并能够运行更加复杂的ModernServlet。<BR 
id=n1mp18>&nbsp;&nbsp;&nbsp; 
本章你建立的连接器是将在第4章详细讨论的Tomcat4的默认连接器的一个简化版本。Tomcat的默认连接器在Tomcat4中是不推荐使用的，但它仍然可以作为一个非常棒的学习工具。在这章的剩余部分，"connector"指的是内置在我们应用程序的模块。<BR 
id=n1mp23>&nbsp;&nbsp;&nbsp; <B>注意</B>：和上一章的应用程序不同的是，本章的应用程序中，连接器和容器是分离的。<BR 
id=aeih0>&nbsp;&nbsp;&nbsp; 本章的应用程序可以在包ex03.pyrmont和它的子包中找到。组成连接器的这些类是包<BR 
id=aeih2>ex03.pyrmont.connector 
和ex03.pyrmont.connector.http的一部分。在本章的开头，每个附带的程序都有个bootstrap类用来启动应用程序。不过，在这个阶段，尚未有一个机制来停止这个应用程序。一旦运行，你必须通过关闭控制台(Windows)或者杀死进程(UNIX/Linux)的方法来鲁 
莽的关闭应用程序。<BR id=aeih7>&nbsp;&nbsp;&nbsp; 
在我们解释该应用程序之前，让我们先来说说包org.apache.catalina.util里边的StringManager类。这个类用来处理这个程序中不同模块和Catalina自身的错误信息的国际化。之后会讨论附带的应用程序。<BR 
id=aeih11>
<H3 id=nqqk>StringManager类 </H3>&nbsp;&nbsp;&nbsp; 
一个像Tomcat这样的大型应用需要仔细的处理错误信息。在Tomcat中，错误信息对于系统管理员和servlet程序员都是有用的。例 
如，Tomcat记录错误信息，让系统管理员可以定位发生的任何异常。对servlet程序员来说，Tomcat会在抛出的任何一个 
javax.servlet.ServletException中发送一个错误信息，这样程序员可以知道他/她的servlet究竟发送什么错误了。<BR 
id=s:8i6>&nbsp;&nbsp;&nbsp; 
Tomcat所采用的方法是在一个属性文件里边存储错误信息，这样，可以容易的修改这些信息。不过，Tomcat中有数以百计的类。把所有类使用的错误信 
息存储到一个大的属性文件里边将会容易产生维护的噩梦。为了避免这一情况，Tomcat为每个包都分配一个属性文件。例如，在包 
org.apache.catalina.connector里边的属性文件包含了该包所有的类抛出的所有错误信息。每个属性文件都会被一个 
org.apache.catalina.util.StringManager类的实例所处理。当Tomcat运行时，将会有许多 
StringManager实例，每个实例会读取包对应的一个属性文件。此外，由于Tomcat的受欢迎程度，提供多种语言的错误信息也是有意义的。目前，有三种语言是被支持的。英语的错误信息属性文件名为LocalStrings.properties。另外两个是西班牙语和日语，分别放在 
LocalStrings_es.properties和LocalStrings_ja.properties里边。<BR 
id=s:8i20>&nbsp;&nbsp;&nbsp; 
当包里边的一个类需要查找放在该包属性文件的一个错误信息时，它首先会获得一个StringManager实例。不过，相同包里边的许多类可能也需要 
StringManager，为每个对象创建一个StringManager实例是一种资源浪费。因此，StringManager类被设计成一个StringManager实例可以被包里边的所有类共享。假如你熟悉设计模式，你将会正确的猜到StringManager是一个单例 
(singleton)类。仅有的一个构造方法是私有的，所有你不能在类的外部使用new关键字来实例化。你通过传递一个包名来调用它的公共静态方法 
getManager来获得一个实例。每个实例存储在一个以包名为键(key)的Hashtable中。<BR id=s:8i31>
<BLOCKQUOTE id=aka4>private static Hashtable managers = new Hashtable();<BR 
  id=qty3>public synchronized static StringManager<BR id=qty30>getManager(String 
  packageName) {<BR id=qty31>&nbsp;&nbsp;&nbsp;&nbsp;StringManager mgr = 
  (StringManager)managers.get(packageName);<BR 
  id=qty32>&nbsp;&nbsp;&nbsp;&nbsp;if (mgr == null) {<BR 
  id=qty33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mgr = new 
  StringManager(packageName);<BR 
  id=qty34>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;managers.put(packageName, 
  mgr);<BR id=qty35>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=qty36>&nbsp;&nbsp;&nbsp;&nbsp;return mgr;<BR id=qty37>}<BR 
id=qty38></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; <B>注意</B>：一篇关于单例模式的题为"The Singleton 
Pattern"的文章可以在附带的ZIP文件中找到。<BR id=aka41>&nbsp;&nbsp;&nbsp; 
例如，要在包ex03.pyrmont.connector.http的一个类中使用StringManager，可以传递包名给StringManager类的getManager方法： 

<BLOCKQUOTE id=rh88>StringManager sm =<BR 
  id=rh880>&nbsp;&nbsp;&nbsp;&nbsp;StringManager.getManager("ex03.pyrmont.connector.http");<BR 
  id=rh881></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在包ex03.pyrmont.connector.http中，你会找到三个属性文件：LocalStrings.properties, 
LocalStrings_es.properties和LocalStrings_ja.properties。StringManager实例是根据运行程序的服务器的区域设置来决定使用哪个文件的。假如你打开LocalStrings.properties，非注释的第一行是这样的：<BR 
id=rh886>
<BLOCKQUOTE id=rh887>httpConnector.alreadyInitialized=HTTP connector has 
  already been initialized<BR id=rh888></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
要获得一个错误信息，可以使用StringManager类的getString，并传递一个错误代号。这是其中一个重载方法：<BR id=rh8810>
<BLOCKQUOTE id=th.j>public String getString(String key)<BR 
id=rh8811></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
通过传递httpConnector.alreadyInitialized作为getString的参数，将会返回"HTTP connector has 
already been initialized"。<BR id=th.j1>
<H3 id=th.j2>应用程序<BR id=c1.b></H3>&nbsp;&nbsp;&nbsp; 
从本章开始，每章附带的应用程序都会分成模块。这章的应用程序由三个模块组成：connector,<BR id=th.j4>startup和core。<BR 
id=th.j5>&nbsp;&nbsp;&nbsp; 
startup模块只有一个类，Bootstrap，用来启动应用的。connector模块的类可以分为五组：<BR id=j7al>
<UL id=j7al0>
  <LI id=j7al1>连接器和它的支撑类(HttpConnector和HttpProcessor)。 
  <LI id=j7al2>指代HTTP请求的类(HttpRequest)和它的辅助类。 
  <LI id=j7al3>指代HTTP响应的类(HttpResponse)和它的辅助类。 
  <LI id=j7al4>Facade类(HttpRequestFacade和HttpResponseFacade)。 
  <LI id=j7al5>Constant类<BR id=t8oz></LI></UL>&nbsp;&nbsp;&nbsp; 
core模块由两个类组成：ServletProcessor和StaticResourceProcessor。<BR 
id=j7al7>&nbsp;&nbsp;&nbsp; Figure 
3.1显示了这个应用的类的UML图。为了让图更具可读性，HttpRequest和HttpResponse相关的类给省略了。你可以在我们讨论Request和Response对象的时候分别找到UML图。<BR 
id=q2ef>
<DIV id=id28 style="TEXT-ALIGN: left"><IMG id=eq1g 
style="WIDTH: 472px; HEIGHT: 225px" 
src="HowTomcatWorks中文版.files/dc32cxpz_21d7fmn6cb_b"></DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Figure 
3.1: 应用程序的UML图<BR id=q2ef0>&nbsp;&nbsp;&nbsp; 和Figure 
2.1的UML图相比，第2章中的HttpServer类被分离为两个类：HttpConnector和HttpProcessor，Request被 
HttpRequest所取代，而Response被HttpResponse所取代。同样，本章的应用使用了更多的类。<BR 
id=q2ef4>&nbsp;&nbsp;&nbsp; 
第2章中的HttpServer类的职责是等待HTTP请求并创建请求和响应对象。在本章的应用中，等待HTTP请求的工作交给HttpConnector实例，而创建请求和响应对象的工作交给了HttpProcessor实例。<BR 
id=f1tq2>&nbsp;&nbsp;&nbsp; 
本章中，HTTP请求对象由实现了javax.servlet.http.HttpServletRequest的HttpRequest类来代表。一个 
HttpRequest对象将会给转换为一个HttpServletRequest实例并传递给被调用的servlet的service方法。因此，每个 
HttpRequest实例必须适当增加字段，以便servlet可以使用它们。值需要赋给HttpRequest对象，包括URI，查询字符串，参数，cookies和其他的头部等等。因为连接器并不知道被调用的servlet需要哪个值，所以连接器必须从HTTP请求中解析所有可获得的值。不过，解析一个HTTP请求牵涉昂贵的字符串和其他操作，假如只是解析servlet需要的值的话，连接器就能节省许多CPU周期。例如，假如servlet不 
解析任何一个请求参数(例如不调用javax.servlet.http.HttpServletRequest的getParameter, 
getParameterMap,getParameterNames或者getParameterValues方法)，连接器就不需要从查询字符串或者 
HTTP请求内容中解析这些参数。Tomcat的默认连接器(和本章应用程序的连接器)试图不解析参数直到servlet真正需要它的时候，通过这样来获得更高效率。<BR 
id=f1tq19>&nbsp;&nbsp;&nbsp; 
Tomcat的默认连接器和我们的连接器使用SocketInputStream类来从套接字的InputStream中读取字节流。一个 
SocketInputStream实例对从套接字的getInputStream方法中返回的java.io.InputStream实例进行包装。 
SocketInputStream类提供了两个重要的方法：readRequestLine和readHeader。readRequestLine返回一个HTTP请求的第一行。例如，这行包括了URI，方法和HTTP版本。因为从套接字的输入流中处理字节流意味着只读取一次，从第一个字节到最后一个字节(并且不回退)，因此readHeader被调用之前，readRequestLine必须只被调用一次。readHeader每次被调用来获得一个头部的名/值对，并且应该被重复的调用知道所有的头部被读取到。readRequestLine的返回值是一个HttpRequestLine的实例，而 
readHeader的返回值是一个HttpHeader对象。我们将在下节中讨论类HttpRequestLine和HttpHeader。<BR 
id=f1tq33>&nbsp;&nbsp;&nbsp; 
HttpProcessor对象创建了HttpRequest的实例，因此必须在它们当中增加字段。HttpProcessor类使用它的parse方法 
来解析一个HTTP请求中的请求行和头部。解析出来并把值赋给HttpProcessor对象的这些字段。不过，parse方法并不解析请求内容或者请求 
字符串里边的参数。这个任务留给了HttpRequest对象它们。只是当servlet需要一个参数时，查询字符串或者请求内容才会被解析。<BR 
id=vz435>&nbsp;&nbsp;&nbsp; 
另一个跟上一个应用程序比较的改进是用来启动应用程序的bootstrap类ex03.pyrmont.startup.Bootstrap的出现。<BR 
id=vz437>&nbsp;&nbsp;&nbsp; 我们将会在下面的子节里边详细说明该应用程序：<BR id=wol6>
<UL id=v6xb>
  <LI id=v6xb0>启动应用程序 
  <LI id=v6xb1>连接器 
  <LI id=v6xb2>创建一个HttpRequest对象 
  <LI id=v6xb3>创建一个HttpResponse对象 
  <LI id=v6xb4>静态资源处理器和servlet处理器 
  <LI id=v6xb5>运行应用程序 </LI></UL>
<H3 id=o6mh>启动应用程序 </H3>&nbsp;&nbsp;&nbsp; 
你可以从ex03.pyrmont.startup.Bootstrap类来启动应用程序。这个类在Listing 3.1中给出。<BR 
id=o6mh1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 3.1: 
Bootstrap类<BR id=o6mh2>
<BLOCKQUOTE id=gt8e>package ex03.pyrmont.startup;<BR id=o6mh3>import 
  ex03.pyrmont.connector.http.HttpConnector;<BR id=o6mh4>public final class 
  Bootstrap {<BR id=o6mh5>&nbsp;&nbsp;&nbsp;&nbsp;public static void 
  main(String[] args) {<BR 
  id=o6mh6>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpConnector 
  connector = new HttpConnector();<BR 
  id=o6mh7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connector.start();<BR 
  id=o6mh8>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=o6mh9>}<BR 
id=o6mh10></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
Bootstrap类中的main方法实例化HttpConnector类并调用它的start方法。HttpConnector类在Listing 3.2给出。<BR 
id=o6mh12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 3.2: 
HttpConnector类的start方法<BR id=o6mh13>
<BLOCKQUOTE id=dpw_>package ex03.pyrmont.connector.http;<BR id=dpw_0>import 
  java.io.IOException;<BR id=dpw_1>import java.net.InetAddress;<BR 
  id=dpw_2>import java.net.ServerSocket;<BR id=dpw_3>import java.net.Socket;<BR 
  id=dpw_4>public class HttpConnector implements Runnable {<BR 
  id=dpw_5>&nbsp;&nbsp;&nbsp;&nbsp;boolean stopped;<BR 
  id=dpw_6>&nbsp;&nbsp;&nbsp;&nbsp;private String scheme = "http";<BR 
  id=dpw_7>&nbsp;&nbsp;&nbsp;&nbsp;public String getScheme() {<BR 
  id=dpw_8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return scheme;<BR 
  id=dpw_9>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=dpw_10>&nbsp;&nbsp;&nbsp;&nbsp;public 
  void run() {<BR 
  id=dpw_11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ServerSocket 
  serverSocket = null;<BR 
  id=dpw_12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int port = 8080;<BR 
  id=dpw_13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=dpw_14>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;serverSocket 
  = new<BR 
  id=dpw_15>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ServerSocket(port, 
  1, InetAddress.getByName("127.0.0.1"));<BR 
  id=dpw_16>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=dpw_17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (IOException 
  e) {<BR 
  id=dpw_18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR 
  id=dpw_19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(1);<BR 
  id=dpw_20>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=dpw_21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while (!stopped) 
  {<BR 
  id=dpw_22>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  Accept the next incoming connection from the server socket<BR 
  id=dpw_23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Socket 
  socket = null;<BR 
  id=dpw_24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try 
  {<BR 
  id=dpw_25>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket 
  = serverSocket.accept();<BR 
  id=dpw_26>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  }<BR 
  id=dpw_27>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (Exception e) {<BR 
  id=dpw_28>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue;<BR 
  id=dpw_29>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=dpw_30>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  Hand this socket off to an HttpProcessor<BR 
  id=dpw_31>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpProcessor 
  processor = new HttpProcessor(this);<BR 
  id=dpw_32>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;processor.process(socket);<BR 
  id=dpw_33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=dpw_34>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=dpw_35>&nbsp;&nbsp;&nbsp;&nbsp;public void start() {<BR 
  id=dpw_36>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread thread = new 
  Thread(this);<BR 
  id=dpw_37>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;thread.start ();<BR 
  id=dpw_38>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=dpw_39>}<BR id=dpw_40></BLOCKQUOTE>
<H3 id=oy-4>连接器 </H3>&nbsp;&nbsp;&nbsp; 
ex03.pyrmont.connector.http.HttpConnector类指代一个连接器，职责是创建一个服务器套接字用来等待前来的HTTP请求。这个类在Listing 
3.2中出现。<BR id=dpw_44>&nbsp;&nbsp;&nbsp; 
HttpConnector类实现了java.lang.Runnable，所以它能被它自己的线程专用。当你启动应用程序，一个HttpConnector的实例被创建，并且它的run方法被执行。<BR 
id=dpw_47>&nbsp;&nbsp;&nbsp; <B>注意</B>： 你可以通过读"Working with 
Threads"这篇文章来提醒你自己怎样创建Java线程。<BR id=n6br0>&nbsp;&nbsp;&nbsp; 
run方法包括一个while循环，用来做下面的事情：<BR id=n6br1>
<UL id=n6br2>
  <LI id=n6br3>等待HTTP请求 
  <LI id=n6br4>为每个请求创建个HttpProcessor实例 
  <LI id=n6br5>调用HttpProcessor的process方法 </LI></UL>&nbsp;&nbsp;&nbsp;&nbsp; 
<B>注意</B>：run方法类似于第2章中HttpServer1类的await方法。<BR id=n6br7>&nbsp;&nbsp;&nbsp; 
马上你就会看到HttpConnector类和ex02.pyrmont.HttpServer1类非常相像，除了从 
java.net.ServerSocket类的accept方法中获得一个套接字之后，一个HttpProcessor实例会被创建，并且通过传递该套 
接字给它的process方法调用。<BR id=h0xz2>&nbsp;&nbsp;&nbsp; 
<B>注意</B>：HttpConnector类有另一个方法叫getScheme，用来返回一个scheme(HTTP)。<BR 
id=h0xz4>&nbsp;&nbsp;&nbsp; HttpProcessor类的process方法接受前来的HTTP请求的套接字，会做下面的事情：<BR 
id=h0xz6>1. 创建一个HttpRequest对象。<BR id=h0xz7>2. 创建一个HttpResponse对象。<BR id=h0xz8>3. 
解析HTTP请求的第一行和头部，并放到HttpRequest对象。<BR id=h0xz10>4. 
解析HttpRequest和HttpResponse对象到一个ServletProcessor或者 
StaticResourceProcessor。像第2章里边说的，ServletProcessor调用被请求的servlet的service方 
法，而StaticResourceProcessor发送一个静态资源的内容。<BR id=h0xz14>&nbsp;&nbsp;&nbsp; 
process方法在Listing 3.3给出.<BR id=h0xz15>Listing 3.3: HttpProcessor类process方法<BR 
id=h0xz16>
<BLOCKQUOTE id=hpfi>public void process(Socket socket) {<BR 
  id=hpfi0>&nbsp;&nbsp;&nbsp;&nbsp;SocketInputStream input = null;<BR 
  id=hpfi1>&nbsp;&nbsp;&nbsp;&nbsp;OutputStream output = null;<BR 
  id=hpfi2>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=hpfi3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;input = new 
  SocketInputStream(socket.getInputStream(), 2048);<BR 
  id=hpfi4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output = 
  socket.getOutputStream();<BR 
  id=hpfi5>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// create HttpRequest 
  object and parse<BR 
  id=hpfi6>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request = new 
  HttpRequest(input);<BR 
  id=hpfi7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// create 
  HttpResponse object<BR 
  id=hpfi8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response = new 
  HttpResponse(output);<BR 
  id=hpfi9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setRequest(request);<BR 
  id=hpfi10>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setHeader("Server", 
  "Pyrmont Servlet Container");<BR 
  id=hpfi11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parseRequest(input, 
  output);<BR 
  id=hpfi12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parseHeaders(input);<BR 
  id=hpfi13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//check if this is a 
  request for a servlet or a static resource<BR 
  id=hpfi14>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//a request for a 
  servlet begins with "/servlet/"<BR 
  id=hpfi15>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (request.getRequestURI().startsWith("/servlet/")) {<BR 
  id=hpfi16>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ServletProcessor 
  processor = new ServletProcessor();<BR 
  id=hpfi17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;processor.process(request, 
  response);<BR id=hpfi18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=hpfi19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else {<BR 
  id=hpfi20>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StaticResourceProcessor 
  processor = new<BR 
  id=hpfi21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StaticResourceProcessor();<BR 
  id=hpfi22>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;processor.process(request, 
  response);<BR id=hpfi23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=hpfi24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Close the 
  socket<BR 
  id=hpfi25>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket.close();<BR 
  id=hpfi26>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// no shutdown for 
  this application<BR id=hpfi27>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=hpfi28>&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) {<BR 
  id=hpfi29>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace 
  ();<BR id=hpfi30>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=hpfi31>}<BR 
id=hpfi32></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
process首先获得套接字的输入流和输出流。请注意，在这个方法中，我们适合继承了java.io.InputStream的SocketInputStream类。<BR 
id=hpfi35>
<BLOCKQUOTE id=y2dv>SocketInputStream input = null;<BR id=y2dv0>OutputStream 
  output = null;<BR id=y2dv1>try {<BR id=y2dv2>&nbsp;&nbsp;&nbsp;&nbsp;input = 
  new SocketInputStream(socket.getInputStream(), 2048);<BR 
  id=y2dv3>&nbsp;&nbsp;&nbsp;&nbsp;output = socket.getOutputStream();<BR 
  id=y2dv4></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 然后，它创建一个HttpRequest实例和一个 instance and 
an HttpResponse instance and assigns<BR id=y2dv5>the HttpRequest to the 
HttpResponse.<BR id=y2dv6>
<BLOCKQUOTE id=d3oi>// create HttpRequest object and parse<BR id=d3oi0>request 
  = new HttpRequest(input);<BR id=d3oi1>// create HttpResponse object<BR 
  id=d3oi2>response = new HttpResponse(output);<BR 
  id=d3oi3>response.setRequest(request);<BR 
id=d3oi4></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
本章应用程序的HttpResponse类要比第2章中的Response类复杂得多。举例来说，你可以通过调用他的setHeader方法来发送头部到一个客户端。<BR 
id=d3oi7>
<BLOCKQUOTE id=d3oi8>response.setHeader("Server", "Pyrmont Servlet 
  Container");<BR id=d3oi9></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接下去，process方法调用HttpProcessor类中的两个私有方法来解析请求。<BR id=d3oi11>
<BLOCKQUOTE id=upp:>parseRequest(input, output);<BR id=d3oi12>parseHeaders 
  (input);<BR id=d3oi13></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，它根据请求URI的形式把HttpRequest和HttpResponse对象传给ServletProcessor或者StaticResourceProcessor进行处理。<BR 
id=upp:2>
<BLOCKQUOTE id=upp:3>if (request.getRequestURI().startsWith("/servlet/")) {<BR 
  id=upp:4>&nbsp;&nbsp;&nbsp;&nbsp;ServletProcessor processor = new 
  ServletProcessor();<BR 
  id=upp:5>&nbsp;&nbsp;&nbsp;&nbsp;processor.process(request, response);<BR 
  id=upp:6>}<BR id=upp:7>else {<BR 
  id=upp:8>&nbsp;&nbsp;&nbsp;&nbsp;StaticResourceProcessor processor =<BR 
  id=upp:9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new 
  StaticResourceProcessor();<BR 
  id=upp:10>&nbsp;&nbsp;&nbsp;&nbsp;processor.process(request, response);<BR 
  id=upp:11>}<BR id=upp:12></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 最后，它关闭套接字。<BR 
id=upp:13>
<BLOCKQUOTE id=in0t>socket.close();<BR id=upp:14></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
也要注意的是，HttpProcessor类使用org.apache.catalina.util.StringManager类来发送错误信息：<BR 
id=in0t1>
<BLOCKQUOTE id=in0t2>protected StringManager sm =<BR 
  id=in0t3>&nbsp;&nbsp;&nbsp;&nbsp;StringManager.getManager("ex03.pyrmont.connector.http");<BR 
  id=in0t4></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
HttpProcessor类中的私有方法--parseRequest，parseHeaders和normalize，是用来帮助填充HttpRequest的。这些方法将会在下节"创建一个HttpRequest对象"中进行讨论。<BR 
id=in0t7>
<H4 id=h8:t>创建一个HttpRequest对象 </H4>&nbsp;&nbsp;&nbsp; 
HttpRequest类实现了javax.servlet.http.HttpServletRequest。跟随它的是一个叫做 
HttpRequestFacade的facade类。Figure 3.2显示了HttpRequest类和它的相关类的UML图。<BR 
id=l.7w2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Figure 3.2: 
HttpRequest类和它的相关类<BR id=u:pz>
<DIV id=s3ih style="TEXT-ALIGN: left"><IMG id=u:pz0 
style="WIDTH: 480px; HEIGHT: 171px" 
src="HowTomcatWorks中文版.files/dc32cxpz_22wd7bkhhn_b"></DIV>&nbsp;&nbsp;&nbsp; 
HttpRequest类的很多方法都留空(你需要等到第4章才会完全实现)，但是servlet程序员已经可以从到来的HTTP请求中获得头部，cookies和参数。这三种类型的值被存储在下面几个引用变量中：<BR 
id=u:pz4>
<BLOCKQUOTE id=oaph>protected HashMap headers = new HashMap();<BR 
  id=oaph0>protected ArrayList cookies = new ArrayList();<BR id=oaph1>protected 
  ParameterMap parameters = null;<BR id=oaph2></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
<B>注意</B>：ParameterMap类将会在“获取参数”这节中解释。<BR id=oaph3>&nbsp;&nbsp;&nbsp; 
因此，一个servlet程序员可以从javax.servlet.http.HttpServletRequest中的下列方法中取得正确的返回 
值：getCookies,getDateHeader,getHeader, getHeaderNames, getHeaders, getParameter, 
getPrameterMap,getParameterNames和getParameterValues。就像你在HttpRequest类中看到的一样，一旦你取得了头部，cookies和填充了正确的值的参数，相关的方法的实现是很简单的。<BR 
id=oaph9>&nbsp;&nbsp;&nbsp; 
不用说，这里主要的挑战是解析HTTP请求和填充HttpRequest类。对于头部和cookies，HttpRequest类提供了addHeader和addCookie方法用于HttpProcessor的parseHeaders方法调用。当需要的时候，会使用 
HttpRequest类的parseParameters方法来解析参数。在本节中所有的方法都会被讨论。<BR 
id=oaph15>&nbsp;&nbsp;&nbsp; 因为HTTP请求的解析是一项相当复杂的任务，所以本节会分为以下几个小节：<BR id=oaph17>
<UL id=gp3h>
  <LI id=gp3h0>读取套接字的输入流 
  <LI id=gp3h1>解析请求行 
  <LI id=gp3h2>解析头部 
  <LI id=gp3h3>解析cookies 
  <LI id=gp3h4>获取参数 </LI></UL>
<H4 id=gp3h5>读取套接字的输入流 </H4>&nbsp;&nbsp;&nbsp; 
在第1章和第2章中，你在ex01.pyrmont.HttpRequest和ex02.pyrmont.HttpRequest类中做了一点请求解析。 
你通过调用java.io.InputStream类的read方法获取了请求行，包括方法，URI和HTTP版本：<BR id=gp3h9>
<BLOCKQUOTE id=ds16>byte[] buffer = new byte [2048];<BR id=ds160>try {<BR 
  id=ds161>&nbsp;&nbsp;&nbsp;&nbsp;// input is the InputStream from the 
  socket.<BR id=ds162>&nbsp;&nbsp;&nbsp;&nbsp;i = input.read(buffer);<BR 
  id=ds163>}<BR id=ds164></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
你没有试图为那两个应用程序去进一步解析请求。不过，在本章的应用程序中，你拥有 
ex03.pyrmont.connector.http.SocketInputStream类，这是 
org.apache.catalina.connector.http.SocketInputStream的一个拷贝。这个类提供了方法不仅用来获取请求行，还有请求头部。<BR 
id=ds169>&nbsp;&nbsp;&nbsp; 
你通过传递一个InputStream和一个指代实例使用的缓冲区大小的整数，来构建一个SocketInputStream实例。在本章中，你在 
ex03.pyrmont.connector.http.HttpProcessor的process方法中创建了一个 
SocketInputStream对象，就像下面的代码片段一样：<BR id=waoa2>
<BLOCKQUOTE id=waoa3>SocketInputStream input = null;<BR id=waoa4>OutputStream 
  output = null;<BR id=waoa5>try {<BR id=waoa6>&nbsp;&nbsp;&nbsp;&nbsp;input = 
  new SocketInputStream(socket.getInputStream(), 2048);<BR 
  id=waoa7>&nbsp;&nbsp;&nbsp;&nbsp;...<BR id=waoa8></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
就像前面提到的一样，拥有一个SocketInputStream是为了两个重要方法：readRequestLine和readHeader。请继续往下阅读。<BR 
id=waoa10>
<H4 id=i0xl>解析请求行 </H4>&nbsp;&nbsp;&nbsp; 
HttpProcessor的process方法调用私有方法parseRequest用来解析请求行，例如一个HTTP请求的第一行。这里是一个请求行的例子：<BR 
id=i0xl2>
<BLOCKQUOTE id=i0xl3>GET /myApp/ModernServlet?userName=tarzan&amp;password=pwd 
  HTTP/1.1<BR id=i0xl4></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
请求行的第二部分是URI加上一个查询字符串。在上面的例子中，URI是这样的：<BR id=i0xl6>
<BLOCKQUOTE id=i0xl7>/myApp/ModernServlet<BR 
id=i0xl8></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 另外，在问好后面的任何东西都是查询字符串。因此，查询字符串是这样的：<BR 
id=i0xl10>
<BLOCKQUOTE id=i0xl11>userName=tarzan&amp;password=pwd<BR 
id=i0xl12></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
查询字符串可以包括零个或多个参数。在上面的例子中，有两个参数名/值对，userName/tarzan和password/pwd。在servlet/JSP编程中，参数名jsessionid是用来携带一个会话标识符。会话标识符经常被作为cookie来嵌入，但是程序员可以选择把它嵌入到查询字符串去，例如，当浏览器的cookie被禁用的时候。<BR 
id=i0xl18>&nbsp;&nbsp;&nbsp; 
当parseRequest方法被HttpProcessor类的process方法调用的时候，request变量指向一个HttpRequest实例。parseRequest方法解析请求行用来获得几个值并把这些值赋给HttpRequest对象。现在，让我们来关注一下在Listing 
3.4中的parseRequest方法。<BR 
id=i0xl23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Listing 
3.4：HttpProcessor类中的parseRequest方法 
<BLOCKQUOTE id=tki4>private void parseRequest(SocketInputStream input, 
  OutputStream output)<BR id=nb5h>throws IOException, ServletException {<BR 
  id=nb5h0>&nbsp;&nbsp;&nbsp;&nbsp;// Parse the incoming request line<BR 
  id=nb5h1>&nbsp;&nbsp;&nbsp;&nbsp;input.readRequestLine(requestLine);<BR 
  id=nb5h2>&nbsp;&nbsp;&nbsp;&nbsp;String method =<BR 
  id=nb5h3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new 
  String(requestLine.method, 0, requestLine.methodEnd);<BR 
  id=nb5h4>&nbsp;&nbsp;&nbsp;&nbsp;String uri = null;<BR 
  id=nb5h5>&nbsp;&nbsp;&nbsp;&nbsp;String protocol = new 
  String(requestLine.protocol, 0,<BR 
  id=nb5h6>&nbsp;&nbsp;&nbsp;&nbsp;requestLine.protocolEnd);<BR 
  id=nb5h7>&nbsp;&nbsp;&nbsp;&nbsp;// Validate the incoming request line<BR 
  id=nb5h8>&nbsp;&nbsp;&nbsp;&nbsp;if (method, length () &lt; 1) {<BR 
  id=nb5h9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new 
  ServletException("Missing HTTP request method");<BR 
  id=nb5h10>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h11>&nbsp;&nbsp;&nbsp;&nbsp;else 
  if (requestLine.uriEnd &lt; 1) {<BR 
  id=nb5h12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new 
  ServletException("Missing HTTP request URI");<BR 
  id=nb5h13>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h14>&nbsp;&nbsp;&nbsp;&nbsp;// 
  Parse any query parameters out of the request URI<BR 
  id=nb5h15>&nbsp;&nbsp;&nbsp;&nbsp;int question = requestLine.indexOf("?");<BR 
  id=nb5h16>&nbsp;&nbsp;&nbsp;&nbsp;if (question &gt;= 0) {<BR 
  id=nb5h17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setQueryString(new 
  String(requestLine.uri, question + 1,<BR 
  id=nb5h18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;requestLine.uriEnd - 
  question - 1));<BR 
  id=nb5h19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri = new 
  String(requestLine.uri, 0, question);<BR 
  id=nb5h20>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h21>&nbsp;&nbsp;&nbsp;&nbsp;else 
  {<BR 
  id=nb5h22>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setQueryString(null);<BR 
  id=nb5h23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri = new 
  String(requestLine.uri, 0, requestLine.uriEnd);<BR 
  id=nb5h24>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h25>&nbsp;&nbsp;&nbsp;&nbsp;// 
  Checking for an absolute URI (with the HTTP protocol)<BR 
  id=nb5h26>&nbsp;&nbsp;&nbsp;&nbsp;if (!uri.startsWith("/")) {<BR 
  id=nb5h27>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int pos = 
  uri.indexOf("://");<BR 
  id=nb5h28>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Parsing out 
  protocol and host name<BR 
  id=nb5h29>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (pos != -1) {<BR 
  id=nb5h30>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos 
  = uri.indexOf('/', pos + 3);<BR 
  id=nb5h31>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (pos == -1) {<BR 
  id=nb5h32>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri 
  = "";<BR 
  id=nb5h33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=nb5h34>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else 
  {<BR 
  id=nb5h35>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri 
  = uri.substring(pos);<BR 
  id=nb5h36>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=nb5h37>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=nb5h38>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h39>&nbsp;&nbsp;&nbsp;&nbsp;// 
  Parse any requested session ID out of the request URI<BR 
  id=nb5h40>&nbsp;&nbsp;&nbsp;&nbsp;String match = ";jsessionid=";<BR 
  id=nb5h41>&nbsp;&nbsp;&nbsp;&nbsp;int semicolon = uri.indexOf(match);<BR 
  id=nb5h42>&nbsp;&nbsp;&nbsp;&nbsp;if (semicolon &gt;= 0) {<BR 
  id=nb5h43>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String rest = 
  uri.substring(semicolon + match,length());<BR 
  id=nb5h44>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int semicolon2 = 
  rest.indexOf(';');<BR 
  id=nb5h45>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (semicolon2 &gt;= 
  0) {<BR 
  id=nb5h46>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionId(rest.substring(0, 
  semicolon2));<BR 
  id=nb5h47>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rest 
  = rest.substring(semicolon2);<BR 
  id=nb5h48>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=nb5h49>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else {<BR 
  id=nb5h50>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionId(rest);<BR 
  id=nb5h51>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rest 
  = "";<BR id=nb5h52>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=nb5h53>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionURL(true);<BR 
  id=nb5h54>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri = 
  uri.substring(0, semicolon) + rest;<BR id=nb5h55>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=nb5h56>&nbsp;&nbsp;&nbsp;&nbsp;else {<BR 
  id=nb5h57>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionId(null);<BR 
  id=nb5h58>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionURL(false);<BR 
  id=nb5h59>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h60>&nbsp;&nbsp;&nbsp;&nbsp;// 
  Normalize URI (using String operations at the moment)<BR 
  id=nb5h61>&nbsp;&nbsp;&nbsp;&nbsp;String normalizedUri = normalize(uri);<BR 
  id=nb5h62>&nbsp;&nbsp;&nbsp;&nbsp;// Set the corresponding request 
  properties<BR id=nb5h63>&nbsp;&nbsp;&nbsp;&nbsp;((HttpRequest) 
  request).setMethod(method);<BR 
  id=nb5h64>&nbsp;&nbsp;&nbsp;&nbsp;request.setProtocol(protocol);<BR 
  id=nb5h65>&nbsp;&nbsp;&nbsp;&nbsp;if (normalizedUri != null) {<BR 
  id=nb5h66>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;((HttpRequest) 
  request).setRequestURI(normalizedUri);<BR 
  id=nb5h67>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h68>&nbsp;&nbsp;&nbsp;&nbsp;else 
  {<BR id=nb5h69>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;((HttpRequest) 
  request).setRequestURI(uri);<BR id=nb5h70>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=nb5h71>&nbsp;&nbsp;&nbsp;&nbsp;if (normalizedUri == null) {<BR 
  id=nb5h72>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new 
  ServletException("Invalid URI: " + uri + "'");<BR 
  id=nb5h73>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=nb5h74>}<BR 
id=nb5h75></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
parseRequest方法首先调用SocketInputStream类的readRequestLine方法：<BR id=vymz0>
<BLOCKQUOTE id=fd1j>input.readRequestLine(requestLine);<BR 
id=fd1j0></BLOCKQUOTE>&nbsp;&nbsp;&nbsp;&nbsp;在这里requestLine是HttpProcessor里边的HttpRequestLine的一个实例：<BR 
id=fd1j1>
<BLOCKQUOTE id=fd1j2>private HttpRequestLine requestLine = new 
  HttpRequestLine();<BR id=fd1j3></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
调用它的readRequestLine方法来告诉SocketInputStream去填入HttpRequestLine实例。<BR 
id=fd1j5>&nbsp;&nbsp;&nbsp; 接下去，parseRequest方法获得请求行的方法，URI和协议：<BR id=fd1j7>
<BLOCKQUOTE id=xqfi>String method =<BR id=cgrj>&nbsp;&nbsp;&nbsp;&nbsp;new 
  String(requestLine.method, 0, requestLine.methodEnd);<BR id=cgrj0>String uri = 
  null;<BR id=cgrj1>String protocol = new String(requestLine.protocol, 0, 
  requestLine.protocolEnd);<BR id=cgrj3></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
不过，在URI后面可以有查询字符串，假如存在的话，查询字符串会被一个问好分隔开来。因此，parseRequest方法试图首先获取查询字符串。并调用setQueryString方法来填充HttpRequest对象：<BR 
id=cgrj7>
<BLOCKQUOTE id=q_.p>// Parse any query parameters out of the request URI<BR 
  id=q_.p0>int question = requestLine.indexOf("?");<BR id=q_.p1>if (question 
  &gt;= 0) { // there is a query string.<BR 
  id=q_.p2>&nbsp;&nbsp;&nbsp;&nbsp;request.setQueryString(new 
  String(requestLine.uri, question + 1,<BR 
  id=q_.p3>&nbsp;&nbsp;&nbsp;&nbsp;requestLine.uriEnd - question - 1));<BR 
  id=q_.p4>&nbsp;&nbsp;&nbsp;&nbsp;uri = new String(requestLine.uri, 0, 
  question);<BR id=q_.p5>}<BR id=q_.p6>else {<BR 
  id=q_.p7>&nbsp;&nbsp;&nbsp;&nbsp;request.setQueryString (null);<BR 
  id=q_.p8>&nbsp;&nbsp;&nbsp;&nbsp;uri = new String(requestLine.uri, 0, 
  requestLine.uriEnd);<BR id=q_.p9>}<BR id=q_.p10></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
不过，大多数情况下，URI指向一个相对资源，URI还可以是一个绝对值，就像下面所示：<BR id=q_.p12>
<BLOCKQUOTE id=o20l>http://www.brainysoftware.com/index.html?name=Tarzan<BR 
  id=q_.p13></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; parseRequest方法同样也检查这种情况：<BR id=o20l0>
<BLOCKQUOTE id=o20l1>// Checking for an absolute URI (with the HTTP 
  protocol)<BR id=o20l2>if (!uri.startsWith("/")) {<BR 
  id=o20l3>&nbsp;&nbsp;&nbsp;&nbsp;// not starting with /, this is an absolute 
  URI<BR id=o20l4>&nbsp;&nbsp;&nbsp;&nbsp;int pos = uri.indexOf("://");<BR 
  id=o20l5>&nbsp;&nbsp;&nbsp;&nbsp;// Parsing out protocol and host name<BR 
  id=o20l6>&nbsp;&nbsp;&nbsp;&nbsp;if (pos != -1) {<BR 
  id=o20l7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos = 
  uri.indexOf('/', pos + 3);<BR 
  id=o20l8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (pos == -1) {<BR 
  id=o20l9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri 
  = "";<BR id=o20l10>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=o20l11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else {<BR 
  id=o20l12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri 
  = uri.substring(pos);<BR 
  id=o20l13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=o20l14>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=o20l15>}<BR 
id=o20l16></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，查询字符串也可以包含一个会话标识符，用jsessionid参数名来指代。因此，parseRequest方法也检查一个会话标识符。假如在查询字符串里边找到jessionid，方法就取得会话标识符，并通过调用setRequestedSessionId方法把值交给HttpRequest实例：<BR 
id=o20l21>
<BLOCKQUOTE id=xefa>// Parse any requested session ID out of the request 
  URI<BR id=xefa0>String match = ";jsessionid=";<BR id=xefa1>int semicolon = 
  uri.indexOf(match);<BR id=xefa2>if (semicolon &gt;= 0) {<BR 
  id=xefa3>&nbsp;&nbsp;&nbsp;&nbsp;String rest = uri.substring(semicolon + 
  match.length());<BR id=xefa4>&nbsp;&nbsp;&nbsp;&nbsp;int semicolon2 = 
  rest.indexOf(';');<BR id=xefa5>&nbsp;&nbsp;&nbsp;&nbsp;if (semicolon2 &gt;= 0) 
  {<BR 
  id=xefa6>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionId(rest.substring(0, 
  semicolon2));<BR id=xefa7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rest 
  = rest.substring(semicolon2);<BR id=xefa8>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=xefa9>&nbsp;&nbsp;&nbsp;&nbsp;else {<BR 
  id=xefa10>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionId(rest);<BR 
  id=xefa11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rest = "";<BR 
  id=xefa12>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=xefa13>&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionURL (true);<BR 
  id=xefa14>&nbsp;&nbsp;&nbsp;&nbsp;uri = uri.substring(0, semicolon) + rest;<BR 
  id=xefa15>}<BR id=xefa16>else {<BR 
  id=xefa17>&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionId(null);<BR 
  id=xefa18>&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionURL(false);<BR 
  id=xefa19>}<BR id=xefa20></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
当jsessionid被找到，也意味着会话标识符是携带在查询字符串里边，而不是在cookie里边。因此，传递true给request的 
setRequestSessionURL方法。否则，传递false给setRequestSessionURL方法并传递null给 
setRequestedSessionURL方法。<BR id=xefa24>&nbsp;&nbsp;&nbsp; 
到这个时候，uri的值已经被去掉了jsessionid。<BR id=fp.i>&nbsp;&nbsp;&nbsp; 
接下去，parseRequest方法传递uri给normalize方法，用于纠正“异常”的URI。例如，任何\的出现都会给/替代。假如uri是正确的格式或者异常可以给纠正的话，normalize将会返回相同的或者被纠正后的URI。假如URI不能纠正的话，它将会给认为是非法的并且通常会返回null。在这种情况下(通常返回null)，parseRequest将会在方法的最后抛出一个异常。<BR 
id=zngy4>&nbsp;&nbsp;&nbsp; 最后，parseRequest方法设置了HttpRequest的一些属性：<BR id=zngy5>
<BLOCKQUOTE id=re05>((HttpRequest) request).setMethod(method);<BR 
  id=zngy6>request.setProtocol(protocol);<BR id=zngy7>if (normalizedUri != null) 
  {<BR id=zngy8>&nbsp;&nbsp;&nbsp;&nbsp;((HttpRequest) 
  request).setRequestURI(normalizedUri);<BR id=zngy9>}<BR id=zngy10>else {<BR 
  id=zngy11>&nbsp;&nbsp;&nbsp;&nbsp;((HttpRequest) 
  request).setRequestURI(uri);<BR id=zngy12>}<BR 
id=zngy13></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
还有，假如normalize方法的返回值是null的话，方法将会抛出一个异常：<BR id=jf-30>
<BLOCKQUOTE id=re050>if (normalizedUri == null) {<BR 
  id=jf-31>&nbsp;&nbsp;&nbsp;&nbsp;throw new ServletException("Invalid URI: " + 
  uri + "'");<BR id=jf-32>}<BR id=jf-33></BLOCKQUOTE>
<H4 id=frr2>解析头部 </H4>&nbsp;&nbsp;&nbsp; 
一个HTTP头部是用类HttpHeader来代表的。这个类将会在第4章详细解释，而现在知道下面的内容就足够了：<BR id=frr21>
<UL id=frr22>
  <LI id=frr23>你可以通过使用类的无参数构造方法构造一个HttpHeader实例。<BR id=frr24></LI></UL></DIV>
<UL id=frr26>
  <LI 
  id=frr27>一旦你拥有一个HttpHeader实例，你可以把它传递给SocketInputStream的readHeader方法。假如这里有头部需要读取，readHeader方法将会相应的填充HttpHeader对象。假如再也没有头部需要读取了，HttpHeader实例的nameEnd和valueEnd字段将会置零。</LI></UL>
<UL id=frr28>
  <LI id=frr29>为了获取头部的名称和值，使用下面的方法：</LI></UL>
<UL id=frr210>
  <LI id=frr211>String name = new String(header.name, 0, header.nameEnd); 
</LI></UL>
<UL id=frr212>
  <LI id=frr213>String value = new String(header.value, 0, header.valueEnd); 
  </LI></UL>&nbsp;&nbsp; 
&nbsp;parseHeaders方法包括一个while循环用于持续的从SocketInputStream中读取头部，直到再也没有头部出现为止。循环从构建一个HttpHeader对象开始，并把它传递给类SocketInputStream的readHeader方法：<BR 
id=miak1>
<BLOCKQUOTE id=miak2>HttpHeader header = new HttpHeader();<BR id=miak3>// Read 
  the next header<BR id=miak4>input.readHeader(header);<BR 
id=miak5></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，你可以通过检测HttpHeader实例的nameEnd和valueEnd字段来测试是否可以从输入流中读取下一个头部信息：<BR id=miak7>
<BLOCKQUOTE id=cx_->if (header.nameEnd == 0) {<BR 
  id=miak8>&nbsp;&nbsp;&nbsp;&nbsp;if (header.valueEnd == 0) {<BR 
  id=miak9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;<BR 
  id=miak10>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=miak11>&nbsp;&nbsp;&nbsp;&nbsp;else 
  {<BR id=miak12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new 
  ServletException 
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(sm.getString("httpProcessor.parseHeaders.colon"));<BR 
  id=miak14>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=miak15>}<BR 
id=miak16></BLOCKQUOTE>&nbsp; &nbsp;&nbsp;&nbsp; 
假如存在下一个头部，那么头部的名称和值可以通过下面方法进行检索：<BR id=cx_-0>
<BLOCKQUOTE id=cx_-1>String name = new String(header.name, 0, 
  header.nameEnd);<BR id=cx_-2>String value = new String(header.value, 0, 
  header.valueEnd);<BR id=cx_-3></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
一旦你获取到头部的名称和值，你通过调用HttpRequest对象的addHeader方法来把它加入headers这个HashMap中：<BR id=cx_-5>
<BLOCKQUOTE id=cx_-6>request.addHeader(name, value);<BR 
id=cx_-7></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
一些头部也需要某些属性的设置。例如，当servlet调用javax.servlet.ServletRequest的getContentLength方法的时候，content-length头部的值将被返回。而包含cookies的cookie头部将会给添加到cookie集合中。就这样，下面是其中一些过程：<BR 
id=cx_-12>
<BLOCKQUOTE id=j:59>if (name.equals("cookie")) {<BR 
  id=j:590>&nbsp;&nbsp;&nbsp;&nbsp;... // process cookies here<BR id=j:591>}<BR 
  id=j:592>else if (name.equals("content-length")) {<BR 
  id=j:593>&nbsp;&nbsp;&nbsp;&nbsp;int n = -1;<BR 
  id=j:594>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=j:595>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n = Integer.parseInt 
  (value);<BR id=j:596>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j:597>&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e) {<BR 
  id=j:598>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new 
  ServletException(sm.getString(<BR 
  id=j:599>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"httpProcessor.parseHeaders.contentLength"));<BR 
  id=j:5910>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=j:5911>&nbsp;&nbsp;&nbsp;&nbsp;request.setContentLength(n);<BR 
  id=j:5912>}<BR id=j:5913>else if (name.equals("content-type")) {<BR 
  id=j:5914>&nbsp;&nbsp;&nbsp;&nbsp;request.setContentType(value);<BR 
  id=j:5915>}<BR 
id=j:5916></BLOCKQUOTE>&nbsp;&nbsp;&nbsp;&nbsp;Cookie的解析将会在下一节“解析Cookies”中讨论。<BR 
id=j:5917>
<H4 id=fzj1>解析Cookies 
</H4>&nbsp;&nbsp;&nbsp;&nbsp;Cookies是作为一个Http请求头部通过浏览器来发送的。这样一个头部名为"cookie"并且它的值是一些cookie名/值对。这里是一个包括两个cookie:username和password的cookie头部的例子。<BR 
id=fzj12>
<BLOCKQUOTE id=fzj13>Cookie: userName=budi; password=pwd;<BR 
id=fzj14></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
Cookie的解析是通过类org.apache.catalina.util.RequestUtil的parseCookieHeader方法来处理的。这个方法接受cookie头部并返回一个javax.servlet.http.Cookie数组。数组内的元素数量和头部里边的cookie名/值对个数是一样的。parseCookieHeader方法在Listing 
3.5中列出。<BR id=fzj19>Listing 3.5: The org.apache.catalina.util.RequestUtil 
class's parseCookieHeader method<BR id=fzj110>
<BLOCKQUOTE id=d7dd>public static Cookie[] parseCookieHeader(String header) 
  {<BR id=d7dd0>&nbsp;&nbsp;&nbsp;&nbsp;if ((header == null) || (header.length 0 
  &lt; 1) )<BR id=d7dd1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  (new Cookie[0]);<BR id=d7dd2>&nbsp;&nbsp;&nbsp;&nbsp;ArrayList cookies = new 
  ArrayList();<BR id=d7dd3>&nbsp;&nbsp;&nbsp;&nbsp;while (header.length() &gt; 
  0) {<BR id=d7dd4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int semicolon 
  = header.indexOf(';');<BR 
  id=d7dd5>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (semicolon &lt; 
  0)<BR 
  id=d7dd6>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;semicolon 
  = header.length();<BR 
  id=d7dd7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (semicolon == 
  0)<BR 
  id=d7dd8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<BR 
  id=d7dd9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String token = 
  header.substring(0, semicolon);<BR 
  id=d7dd10>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (semicolon &lt; 
  header.length())<BR 
  id=d7dd11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;header 
  = header.substring(semicolon + 1);<BR 
  id=d7dd12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<BR 
  id=d7dd13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;header 
  = "";<BR id=d7dd14>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=d7dd15>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int 
  equals = token.indexOf('=');<BR 
  id=d7dd16>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (equals &gt; 0) {<BR 
  id=d7dd17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String 
  name = token.substring(0, equals).trim();<BR 
  id=d7dd18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String 
  value = token.substring(equals+1).trim();<BR 
  id=d7dd19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cookies.add(new 
  Cookie(name, value));<BR 
  id=d7dd20>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=d7dd21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=d7dd22>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Throwable e) 
  {<BR 
  id=d7dd23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;;<BR 
  id=d7dd24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=d7dd25>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=d7dd26>&nbsp;&nbsp;&nbsp;&nbsp;return ((Cookie[]) cookies.toArray (new 
  Cookie [cookies.size ()]));<BR id=d7dd27>}<BR 
id=d7dd28></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
还有，这里是HttpProcessor类的parseHeader方法中用于处理cookie的部分代码:<BR id=d7dd30>
<BLOCKQUOTE id=x_sf>else if (header.equals(DefaultHeaders.COOKIE_NAME)) {<BR 
  id=x_sf0>&nbsp;&nbsp;&nbsp;&nbsp;Cookie cookies[] = 
  RequestUtil.ParseCookieHeader (value);<BR id=x_sf1>&nbsp;&nbsp;&nbsp;&nbsp;for 
  (int i = 0; i &lt; cookies.length; i++) {<BR 
  id=x_sf2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (cookies[i].getName().equals("jsessionid")) {<BR 
  id=x_sf3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  Override anything requested in the URL<BR 
  id=x_sf4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (!request.isRequestedSessionIdFromCookie()) {<BR 
  id=x_sf5>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 
  Accept only the first session id cookie<BR 
  id=x_sf6>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionId(cookies[i].getValue());<BR 
  id=x_sf7>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionCookie(true);<BR 
  id=x_sf8>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.setRequestedSessionURL(false);<BR 
  id=x_sf9>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x_sf10>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=x_sf11>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.addCookie(cookies[i]);<BR 
  id=x_sf12>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=x_sf13>}<BR id=x_sf14></BLOCKQUOTE>
<H4 id=egb7>获取参数 </H4>&nbsp;&nbsp;&nbsp; 
你不需要马上解析查询字符串或者HTTP请求内容，直到servlet需要通过调用javax.servlet.http.HttpServletRequest的getParameter,<BR 
id=egb71>getParameterMap, 
getParameterNames或者getParameterValues方法来读取参数。因此，HttpRequest的这四个方法开头调用了parseParameter方法。<BR 
id=egb74>&nbsp;&nbsp;&nbsp; 
这些参数只需要解析一次就够了，因为假如参数在请求内容里边被找到的话，参数解析将会使得SocketInputStream到达字节流的尾部。类HttpRequest使用一个布尔变量parsed来指示是否已经解析过了。<BR 
id=egb78>&nbsp;&nbsp;&nbsp; 
参数可以在查询字符串或者请求内容里边找到。假如用户使用GET方法来请求servlet的话，所有的参数将在查询字符串里边出现。假如使用POST方法的话，你也可以在请求内容中找到一些。所有的名/值对将会存储在一个HashMap里边。Servlet程序员可以以Map的形式获得参数(通过调用HttpServletRequest的getParameterMap方法)和参数名/值。There 
is a catch, though. 
Servlet程序员不被允许修改参数值。因此，将使用一个特殊的HashMap：org.apache.catalina.util.ParameterMap。<BR 
id=mkfz6>&nbsp;&nbsp;&nbsp; 
类ParameterMap继承java.util.HashMap，并使用了一个布尔变量locked。当locked是false的时候，名/值对仅仅可以添加，更新或者移除。否则，异常IllegalStateException会抛出。而随时都可以读取参数值。<BR 
id=t8uy1>类ParameterMap将会在Listing 
3.6中列出。它覆盖了方法用于增加，更新和移除值。那些方法仅仅在locked为false的时候可以调用。<BR id=t8uy4>Listing 3.6: 
The org.apache.Catalina.util.ParameterMap class.<BR id=t8uy5>
<BLOCKQUOTE id=lf79>package org.apache.catalina.util;<BR id=lf790>import 
  java.util.HashMap;<BR id=lf791>import java.util.Map;<BR id=lf792>public final 
  class ParameterMap extends HashMap {<BR 
  id=lf793>&nbsp;&nbsp;&nbsp;&nbsp;public ParameterMap() {<BR 
  id=lf794>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super ();<BR 
  id=lf795>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=lf796>&nbsp;&nbsp;&nbsp;&nbsp;public 
  ParameterMap(int initialCapacity) {<BR 
  id=lf797>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(initialCapacity);<BR 
  id=lf798>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=lf799>&nbsp;&nbsp;&nbsp;&nbsp;public 
  ParameterMap(int initialCapacity, float loadFactor) {<BR 
  id=lf7910>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(initialCapacity, 
  loadFactor);<BR id=lf7911>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=lf7912>&nbsp;&nbsp;&nbsp;&nbsp;public ParameterMap(Map map) {<BR 
  id=lf7913>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(map);<BR 
  id=lf7914>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=lf7915>&nbsp;&nbsp;&nbsp;&nbsp;private boolean locked = false;<BR 
  id=lf7916>&nbsp;&nbsp;&nbsp;&nbsp;public boolean isLocked() {<BR 
  id=lf7917>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  (this.locked);<BR id=lf7918>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=lf7919>&nbsp;&nbsp;&nbsp;&nbsp;public void setLocked(boolean locked) {<BR 
  id=lf7920>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.locked = 
  locked;<BR id=lf7921>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=lf7922>&nbsp;&nbsp;&nbsp;&nbsp;private static final StringManager sm =<BR 
  id=lf7923>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StringManager.getManager("org.apache.catalina.util");<BR 
  id=lf7924>&nbsp;&nbsp;&nbsp;&nbsp;public void clear() {<BR 
  id=lf7925>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (locked)<BR 
  id=lf7926>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw 
  new IllegalStateException<BR 
  id=lf7927>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(sm.getString("parameterMap.locked"));<BR 
  id=lf7928>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super.clear();<BR 
  id=lf7929>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=lf7930>&nbsp;&nbsp;&nbsp;&nbsp;public Object put(Object key, Object value) 
  {<BR id=lf7931>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (locked)<BR 
  id=lf7932>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw 
  new IllegalStateException<BR 
  id=lf7933>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(sm.getString("parameterMap.locked"));<BR 
  id=lf7934>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  (super.put(key, value));<BR id=lf7935>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=lf7936>&nbsp;&nbsp;&nbsp;&nbsp;public void putAll(Map map) {<BR 
  id=lf7937>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (locked)<BR 
  id=lf7938>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw 
  new IllegalStateException<BR 
  id=lf7939>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(sm.getString("parameterMap.locked"));<BR 
  id=lf7940>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super.putAll(map);<BR 
  id=lf7941>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=lf7942>&nbsp;&nbsp;&nbsp;&nbsp;public Object remove(Object key) {<BR 
  id=lf7943>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (locked)<BR 
  id=lf7944>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw 
  new IllegalStateException<BR 
  id=lf7945>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(sm.getString("parameterMap.locked"));<BR 
  id=lf7946>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 
  (super.remove(key));<BR id=lf7947>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=lf7948>}<BR 
  id=lf7949></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 现在，让我们来看parseParameters方法是怎么工作的。<BR 
id=lf7950>&nbsp;&nbsp;&nbsp; 
因为参数可以存在于查询字符串或者HTTP请求内容中，所以parseParameters方法会检查查询字符串和请求内容。一旦解析过后，参数将会在对象变量parameters中找到，所以方法的开头会检查parsed布尔变量，假如已经解析过的话，parsed将会返回true。<BR 
id=t2v52>
<BLOCKQUOTE id=t2v53>if (parsed)<BR id=t2v54>return;<BR 
id=t2v55></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，parseParameters方法创建一个名为results的ParameterMap变量，并指向parameters。假如<BR 
id=t2v56>parameters为null的话，它将创建一个新的ParameterMap。<BR id=t2v57>
<BLOCKQUOTE id=t2v58>ParameterMap results = parameters;<BR id=t2v59>if 
  (results == null)<BR id=t2v510>&nbsp;&nbsp;&nbsp;&nbsp;results = new 
  ParameterMap();<BR id=t2v511></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，parseParameters方法打开parameterMap的锁以便写值。<BR id=t2v513>
<BLOCKQUOTE id=qn63>results.setLocked(false);<BR 
id=t2v514></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
下一步，parseParameters方法检查字符编码，并在字符编码为null的时候赋予默认字符编码。<BR id=ir160>
<BLOCKQUOTE id=qn630>String encoding = getCharacterEncoding();<BR id=ir161>if 
  (encoding == null)<BR id=ir162>&nbsp;&nbsp;&nbsp;&nbsp;encoding = 
  "ISO-8859-1";<BR id=ir163></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，parseParameters方法尝试解析查询字符串。解析参数是使用org.apache.Catalina.util.RequestUtil的parseParameters方法来处理的。<BR 
id=qn632>
<BLOCKQUOTE id=nm5z>// Parse any parameters specified in the query string<BR 
  id=qn633>String queryString = getQueryString();<BR id=qn634>try {<BR 
  id=qn635>&nbsp;&nbsp;&nbsp;&nbsp;RequestUtil.parseParameters(results, 
  queryString, encoding);<BR id=qn636>}<BR id=qn637>catch 
  (UnsupportedEncodingException e) {<BR id=qn638>&nbsp;&nbsp;&nbsp;&nbsp;;<BR 
  id=qn639>}<BR id=qn6310></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接下来，方法尝试查看HTTP请求内容是否包含参数。这种情况发生在当用户使用POST方法发送请求的时候，内容长度大于零，并且内容类型是application/x-www-form-urlencoded的时候。所以，这里是解析请求内容的代码：<BR 
id=nm5z3>
<BLOCKQUOTE id=hb9q>// Parse any parameters specified in the input stream<BR 
  id=onwe>String contentType = getContentType();<BR id=onwe0>if (contentType == 
  null)<BR id=onwe1>&nbsp;&nbsp;&nbsp;&nbsp;contentType = "";<BR id=onwe2>int 
  semicolon = contentType.indexOf(';');<BR id=onwe3>if (semicolon &gt;= 0) {<BR 
  id=onwe4>&nbsp;&nbsp;&nbsp;&nbsp;contentType = contentType.substring (0, 
  semicolon).trim();<BR id=onwe5>}<BR id=onwe6>else {<BR 
  id=onwe7>&nbsp;&nbsp;&nbsp;&nbsp;contentType = contentType.trim();<BR 
  id=onwe8>}<BR id=onwe9>if ("POST".equals(getMethod()) &amp;&amp; 
  (getContentLength() &gt; 0)<BR id=onwe10>&nbsp;&nbsp;&nbsp;&nbsp;&amp;&amp; 
  "application/x-www-form-urlencoded".equals(contentType)) {<BR 
  id=onwe11>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR 
  id=onwe12>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int max = 
  getContentLength();<BR 
  id=onwe13>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int len = 0;<BR 
  id=onwe14>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;byte buf[] = new 
  byte[getContentLength()];<BR 
  id=onwe15>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ServletInputStream 
  is = getInputStream();<BR 
  id=onwe16>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while (len &lt; max) 
  {<BR 
  id=onwe17>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int 
  next = is.read(buf, len, max - len);<BR 
  id=onwe18>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if 
  (next &lt; 0 ) {<BR 
  id=onwe19>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<BR 
  id=onwe20>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=onwe21>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;len 
  += next;<BR id=onwe22>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=onwe23>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;is.close();<BR 
  id=onwe24>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (len &lt; max) 
  {<BR 
  id=onwe25>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw 
  new RuntimeException("Content length mismatch");<BR 
  id=onwe26>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=onwe27>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RequestUtil.parseParameters(results, 
  buf, encoding);<BR id=onwe28>&nbsp;&nbsp;&nbsp;&nbsp;}<BR 
  id=onwe29>&nbsp;&nbsp;&nbsp;&nbsp;catch (UnsupportedEncodingException ue) {<BR 
  id=onwe30>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;;<BR 
  id=onwe31>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=onwe32>&nbsp;&nbsp;&nbsp;&nbsp;catch 
  (IOException e) {<BR 
  id=onwe33>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new 
  RuntimeException("Content read fail");<BR 
  id=onwe34>&nbsp;&nbsp;&nbsp;&nbsp;}<BR id=onwe35>}<BR 
id=onwe36></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
最后，parseParameters方法锁定ParameterMap，设置parsed为true，并把results赋予parameters。<BR 
id=lq.n0>
<BLOCKQUOTE id=lq.n1>// Store the final results<BR 
  id=lq.n2>results.setLocked(true);<BR id=lq.n3>parsed = true;<BR 
  id=lq.n4>parameters = results;<BR id=lq.n5></BLOCKQUOTE>
<H4 id=lq.n6>创建一个HttpResponse对象 </H4>&nbsp;&nbsp;&nbsp; 
HttpResponse类实现了javax.servlet.http.HttpServletResponse。跟随它的是一个叫做HttpResponseFacade的façade类。Figure 
3.3显示了HttpResponse类和它的相关类的UML图。<BR id=lq.n9>
<DIV id=v.j4 style="TEXT-ALIGN: center"><IMG style="WIDTH: 462px; HEIGHT: 172px" 
src="HowTomcatWorks中文版.files/dc32cxpz_34cp3g834m_b"></DIV>&nbsp;&nbsp;&nbsp; 
在第2章中，你使用的是一个部分实现的HttpResponse类。例如，它的getWriter方法，在它的其中一个print方法被调用的时候，返回一个不会自动清除的java.io.PrintWriter对象。在本章中应用程序将会修复这个问题。为了理解它是如何修复的，你需要知道Writer是什么东西来的。<BR>&nbsp;&nbsp;&nbsp; 
在一个servlet里边，你使用PrintWriter来写字节。你可以使用任何你希望的编码，但是这些字节将会以字节流的形式发送到浏览器去。因此，第2章中ex02.pyrmont.HttpResponse类的getWriter方法就不奇怪了：<BR>
<BLOCKQUOTE>public PrintWriter getWriter() {<BR>// if autoflush is true, 
  println() will flush,<BR>// but print() will not.<BR>// the output argument is 
  an OutputStream<BR>&nbsp;&nbsp; &nbsp;writer = new PrintWriter(output, 
  true);<BR>&nbsp;&nbsp; &nbsp;return 
writer;<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
请看，我们是如何构造一个PrintWriter对象的?就是通过传递一个java.io.OutputStream实例来实现的。你传递给PrintWriter的print或println方法的任何东西都是通过底下的OutputStream进行发送的。<BR>&nbsp;&nbsp;&nbsp; 
在本章中，你为PrintWriter使用ex03.pyrmont.connector.ResponseStream类的一个实例来替代<BR>OutputStream。需要注意的是，类ResponseStream是间接的从类java.io.OutputStream传递过去的。<BR>&nbsp;&nbsp;&nbsp; 
同样的你使用了继承于PrintWriter的类ex03.pyrmont.connector.ResponseWriter。<BR>类ResponseWriter覆盖了所有的print和println方法，并且让这些方法的任何调用把输出自动清除到底下的<BR>OutputStream去。因此，我们使用一个带底层ResponseStream对象的ResponseWriter实例。<BR>&nbsp;&nbsp;&nbsp; 
我们可以通过传递一个ResponseStream对象实例来初始化类ResponseWriter。然而，我们使用一个java.io.OutputStreamWriter对象充当ResponseWriter对象和ResponseStream对象之间的桥梁。<BR>&nbsp;&nbsp;&nbsp; 
通过OutputStreamWriter，写进去的字符通过一种特定的字符集被编码成字节。这种字符集可以使用名字来设定，或者明确给出，或者使用平台可接受的默认字符集。write方法的每次调用都会导致在给定的字符上编码转换器的调用。在写入底层的输出流之前，生成的字节都会累积到一个缓冲区中。缓冲区的大小可以自己设定，但是对大多数场景来说，默认的就足够大了。注意的是，传递给write方法的字符是没有被缓冲的。<BR>&nbsp;&nbsp;&nbsp; 
因此，getWriter方法如下所示:<BR>
<BLOCKQUOTE>public PrintWriter getWriter() throws IOException 
  {<BR>&nbsp;&nbsp; &nbsp;ResponseStream newStream = new 
  ResponseStream(this);<BR>&nbsp;&nbsp; 
  &nbsp;newStream.setCommit(false);<BR>&nbsp;&nbsp; &nbsp;OutputStreamWriter osr 
  =<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;new OutputStreamWriter(newStream, 
  getCharacterEncoding());<BR>&nbsp;&nbsp; &nbsp;writer = new 
  ResponseWriter(osr);<BR>&nbsp;&nbsp; &nbsp;return writer;<BR>}<BR></BLOCKQUOTE>
<H4>静态资源处理器和Servlet处理器</H4>&nbsp;&nbsp;&nbsp; 
类ServletProcessor类似于第2章中的类ex02.pyrmont.ServletProcessor。它们都只有一个方法：process。然而ex03.pyrmont.connector.ServletProcessor中的process方法接受一个HttpRequest和<BR>HttpResponse，代替了Requese和Response实例。下面是本章中process的方法签名：<BR>
<BLOCKQUOTE>public void process(HttpRequest request, HttpResponse response) 
  {<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
另外，process方法使用HttpRequestFacade和HttpResponseFacade作为<BR>request和response的facade类。另外，在调用了servlet的service方法之后，它调用了类HttpResponse的<BR>finishResponse方法。<BR>
<BLOCKQUOTE>servlet = (Servlet) myClass.newInstance();<BR>HttpRequestFacade 
  requestPacade = new HttpRequestFacade(request);<BR>HttpResponseFacade 
  responseFacade = new 
  HttpResponseFacade(response);<BR>servlet.service(requestFacade, 
  responseFacade);<BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">((HttpResponse) 
  response).finishResponse();</SPAN><BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
类StaticResourceProcessor几乎等同于类ex02.pyrmont.StaticResourceProcessor。<BR>
<H3>运行应用程序</H3>&nbsp;&nbsp; &nbsp;要在Windows上运行该应用程序，在工作目录下面敲入以下命令：<BR>
<BLOCKQUOTE>java -classpath ./lib/servlet.jar;./ 
  ex03.pyrmont.startup.Bootstrap<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在Linux下，你使用一个冒号来分隔两个库：<BR>
<BLOCKQUOTE>java -classpath ./lib/servlet.jar:./ 
  ex03.pyrmont.startup.Bootstrap<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
要显示index.html，使用下面的URL:<BR>
<BLOCKQUOTE>http://localhost:808O/index.html<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
要调用PrimitiveServlet，让浏览器指向下面的URL：<BR>
<BLOCKQUOTE>http://localhost:8080/servlet/PrimitiveServlet<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在你的浏览器中将会看到下面的内容：<BR>
<BLOCKQUOTE>Hello. Roses are red.<BR>Violets are 
blue.<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
<B>注意：</B>在第2章中运行PrimitiveServlet不会看到第二行。<BR>&nbsp;&nbsp;&nbsp; 
你也可以调用ModernServet，在第2章中它不能运行在servlet容器中。下面是相应的URL：<BR>
<BLOCKQUOTE>http://localhost:8080/servlet/ModernServlet<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
<B>注意：</B>ModernServlet的源代码在工作目录的webroot文件夹可以找到。<BR>&nbsp;&nbsp;&nbsp; 
你可以加上一个查询字符串到URL中去测试servlet。加入你使用下面的URL来运行ModernServlet的话，将显示Figure 
3.4中的运行结果。<BR>
<BLOCKQUOTE>http://localhost:8080/servlet/ModernServlet?userName=tarzan&amp;password=pwd<BR></BLOCKQUOTE>
<DIV id=bva- style="TEXT-ALIGN: left">
<DIV id=ffd4 style="TEXT-ALIGN: left"><IMG style="WIDTH: 456px; HEIGHT: 497px" 
src="HowTomcatWorks中文版.files/dc32cxpz_36cc3g2gjv_b"></DIV></DIV>Figure 3.4: 
Running ModernServlet<BR>
<H3>总结</H3>&nbsp;&nbsp;&nbsp; 
在本章中，你已经知道了连接器是如何工作的。建立起来的连接器是Tomcat4的默认连接器的简化版本。正如你所知道的，因为默认连接器并不高效，所以已经被弃用了。例如，所有的HTTP请求头部都被解析了，即使它们没有在servlet中使用过。因此，默认连接器很慢，并且已经被Coyote所代替了。Coyote是一个更快的连接器，它的源代码可以在Apache软件基金会的网站中下载。不管怎样，默认连接器作为一个优秀的学习工具，将会在第4章中详细讨论。<BR><BR 
id=laxe6>
<H2>第四章:Tomcat的默认连接器</H2>
<H3>概要<BR></H3>&nbsp;&nbsp;&nbsp; 
第3章的连接器运行良好，可以完善以获得更好的性能。但是，它只是作为一个教育工具，设计来介绍Tomcat4的默认连接器用的。理解第3章中的连接器是理解Tomcat4的默认连接器的关键所在。现在，在第4章中将通过剖析Tomcat4的默认连接器的代码，讨论需要什么来创建一个真实的Tomcat连接器。<BR><B>注意：</B>本章中提及的“默认连接器”是指Tomcat4的默认连接器。即使默认的连机器已经被弃用，被更快的，代号为Coyote的连接器所代替，它仍然是一个很好的学习工具。<BR>&nbsp;&nbsp;&nbsp; 
Tomcat连接器是一个可以插入servlet容器的独立模块，已经存在相当多的连接器了，包括Coyote, mod_jk, 
mod_jk2和mod_webapp。一个Tomcat连接器必须符合以下条件：<BR>1. 
必须实现接口org.apache.catalina.Connector。<BR>2. 
必须创建请求对象，该请求对象的类必须实现接口org.apache.catalina.Request。<BR>3. 
必须创建响应对象，该响应对象的类必须实现接口org.apache.catalina.Response。<BR>&nbsp;&nbsp;&nbsp; 
Tomcat4的默认连接器类似于第3章的简单连接器。它等待前来的HTTP请求，创建request和response对象，然后把request和response对象传递给容器。连接器是通过调用接口org.apache.catalina.Container的invoke方法来传递request和response对象的。invoke的方法签名如下所示：<BR>
<BLOCKQUOTE>public void invoke(<BR>&nbsp;&nbsp; 
  &nbsp;org.apache.catalina.Request request,<BR>&nbsp;&nbsp; 
  &nbsp;org.apache.catalina.Response response);<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在invoke方法里边，容器加载servlet，调用它的service方法，管理会话，记录出错日志等等。<BR>&nbsp;&nbsp;&nbsp; 
默认连接器同样使用了一些第3章中的连接器未使用的优化。首先就是提供一个各种各样对象的对象池用于避免昂贵对象的创建。接着，在很多地方使用字节数组来代替字符串。<BR>&nbsp;&nbsp;&nbsp; 
本章中的应用程序是一个和默认连接器管理的简单容器。然而，本章的焦点不是简单容器而是默认连接器。我们将会在第5章中讨论容器。不管怎样，为了展示如何使用默认连接器，将会在接近本章末尾的“简单容器的应用程序”一节中讨论简单容器。<BR>&nbsp;&nbsp;&nbsp; 
另一个需要注意的是默认连接器除了提供HTTP0.9和HTTP1.0的支持外，还实现了HTTP1.1的所有新特性。为了理解HTTP1.1中的新特性，你首先需要理解本章首节解释的这些新特性。在这之后，我们将会讨论接口<BR>org.apache.catalina.Connector和如何创建请求和响应对象。假如你理解第3章中连接器如何工作的话，那么在理解默认连接器的时候你应该不会遇到任何问题。<BR>&nbsp;&nbsp;&nbsp; 
本章首先讨论HTTP1.1的三个新特性。理解它们是理解默认连接器内部工作机制的关键所在。然后，介绍所有连接器都会实现的接口 
org.apache.catalina.Connector。你会发现第3章中遇到的那些类，例如HttpConnector, 
HttpProcessor等等。不过，这个时候，它们比第3章那些类似的要高级些。<BR>
<H3>HTTP 1.1新特性</H3>&nbsp;&nbsp;&nbsp; 
本节解释了HTTP1.1的三个新特性。理解它们是理解默认连接器如何处理HTTP请求的关键。<BR>
<H4>持久连接</H4>&nbsp;&nbsp;&nbsp; 
在HTTP1.1之前，无论什么时候浏览器连接到一个web服务器，当请求的资源被发送之后，连接就被服务器关闭了。然而，一个互联网网页包括其他资源， 
例如图片文件，applet等等。因此，当一个页面被请求的时候,浏览器同样需要下载页面所引用到的资源。加入页面和它所引用到的全部资源使用不同连接来 
下载的话，进程将会非常慢。那就是为什么HTTP1.1引入持久连接的原因了。使用持久连接的时候，当页面下载的时候，服务器并不直接关闭连接。相反，它 
等待web客户端请求页面所引用的全部资源。这种情况下，页面和所引用的资源使用同一个连接来下载。考虑建立和解除HTTP连接的宝贵操作的话，这就为 
web服务器，客户端和网络节省了许多工作和时间。<BR>&nbsp;&nbsp;&nbsp; 
持久连接是HTTP1.1的默认连接方式。同样，为了明确这一点，浏览器可以发送一个值为keep-alive的请求头部connection:<BR>
<BLOCKQUOTE>connection: keep-alive<BR></BLOCKQUOTE>
<H4>块编码</H4>&nbsp;&nbsp;&nbsp; 
建立持续连接的结果就是，使用同一个连接，服务器可以从不同的资源发送字节流，而客户端可以使用发送多个请求。结果就是，发送方必须为每个请求或响应发送 
内容长度的头部，以便接收方知道如何解释这些字节。然而，大部分的情况是发送方并不知道将要发送多少个字节。例如，在开头一些字节已经准备好的时 
候，servlet容器就可以开始发送响应了，而不会等到所有都准备好。这意味着，在content-length头部不能提前知道的情况下，必须有一种 
方式来告诉接收方如何解释字节流。<BR>&nbsp;&nbsp;&nbsp; 
即使不需要发送多个请求或者响应，服务器或者客户端也不需要知道将会发送多少数据。在HTTP1.0中，服务器可以仅仅省略content-length 
头部，并保持写入连接。当写入完成的时候，它将简单的关闭连接。在这种情况下，客户端将会保持读取状态，直到获取到-1，表示已经到达文件的尾部。<BR>&nbsp;&nbsp;&nbsp; 
HTTP1.1使用一个特别的头部transfer-encoding来表示有多少以块形式的字节流将会被发送。对每块来说，在数据之前，长度(十六进 
制)后面接着CR/LF将被发送。整个事务通过一个零长度的块来标识。假设你想用2个块发送以下38个字节，第一个长度是29，第二个长度是9。<BR>
<BLOCKQUOTE>I'm as helpless as a kitten up a 
tree.<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 你将这样发送：<BR>
<BLOCKQUOTE>1D\r\n<BR>I'm as helpless as a kitten u<BR>9\r\n<BR>p a 
  tree.<BR>0\r\n<BR></BLOCKQUOTE>&nbsp;&nbsp; 
&nbsp;1D,是29的十六进制，指示第一块由29个字节组成。0\r\n标识这个事务的结束。<BR>
<H4>状态100(持续状态)的使用</H4>&nbsp;&nbsp;&nbsp; 在发送请求内容之前，HTTP 1.1客户端可以发送Expect: 
100-continue头部到服务器，并等待服务器的确认。这个一般发生在当客户端需要发送一份长的请求内容而未能确保服务器愿意接受它的时候。如果你 
发送一份长的请求内容仅仅发现服务器拒绝了它，那将是一种浪费来的。<BR>&nbsp;&nbsp;&nbsp; 当接受到Expect: 
100-continue头部的时候，假如乐意或者可以处理请求的话，服务器响应100-continue头部，后边跟着两对CRLF字符。
<BLOCKQUOTE>HTTP/1.1 100 Continue<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接着，服务器应该会继续读取输入流。<BR>
<H3>Connector接口</H3>&nbsp;&nbsp;&nbsp; 
Tomcat连接器必须实现org.apache.catalina.Connector接口。在这个接口的众多方法中，最重要的是getContainer,setContainer, 
createRequest和createResponse。<BR>&nbsp;&nbsp; 
&nbsp;setContainer是用来关联连接器和容器用的。getContainer返回关联的容器。createRequest为前来的HTTP请求构造一个请求对象，而createResponse创建一个响应对象。<BR>&nbsp;&nbsp;&nbsp; 
类org.apache.catalina.connector.http.HttpConnector是Connector接口的一个实现，将会在下一 
节“HttpConnector类”中讨论。现在，仔细看一下Figure 
4.1中的默认连接器的UML类图。注意的是，为了保持图的简单化，Request和Response接口的实现被省略了。除了 
SimpleContainer类，org.apache.catalina前缀也同样从类型名中被省略了。<BR>
<DIV id=e4uc style="TEXT-ALIGN: left"><IMG style="WIDTH: 543px; HEIGHT: 294px" 
src="HowTomcatWorks中文版.files/dc32cxpz_38f94t33hf_b"></DIV>Figure 4.1: The 
default connector class diagram<BR>&nbsp;&nbsp;&nbsp; 
因此，Connector需要被org.apache.catalina.Connector,util.StringManager<BR>org.apache.catalina.util.StringManager等等访问到。<BR>&nbsp;&nbsp;&nbsp; 
一个Connector和Container是一对一的关系。箭头的方向显示出Connector知道Container但反过来就不成立了。同样需要注意的是，不像第3章的是，HttpConnector和HttpProcessor是一对多的关系。<BR>
<H4>HttpConnector类</H4>&nbsp;&nbsp;&nbsp; 
由于在第3章中org.apache.catalina.connector.http.HttpConnector的简化版本已经被解释过了，所以你已 
经知道这个类是怎样的了。它实现了org.apache.catalina.Connector 
(为了和Catalina协调),<BR>java.lang.Runnable 
(因此它的实例可以运行在自己的线程上)和org.apache.catalina.Lifecycle。接口Lifecycle用来维护每个已经实现它的Catalina组件的生命周期。<BR>&nbsp;&nbsp; 
&nbsp;Lifecycle将在第6章中解释，现在你不需要担心它，只要明白通过实现Lifecycle,在你创建HttpConnector实例之后，你应该 
调用它的initialize和start方法。这两个方法在组件的生命周期里必须只调用一次。我们将看看和第3章的HttpConnector类的那些 
不同方面:HttpConnector如何创建一个服务器套接字，它如何维护一个HttpProcessor对象池，还有它如何处理HTTP请求。
<H4>创建一个服务器套接字</H4>&nbsp;&nbsp;&nbsp; 
HttpConnector的initialize方法调用open这个私有方法，返回一个java.net.ServerSocket实例，并把它赋予 
serverSocket。然而，不是调用java.net.ServerSocket的构造方法，open方法是从一个服务端套接字工厂中获得一个 
ServerSocket实例。如果你想知道这工厂的详细信息，可以阅读包org.apache.catalina.net里边的接口 
ServerSocketFactory和类DefaultServerSocketFactory。它们是很容易理解的。
<H4>维护HttpProcessor实例</H4>&nbsp;&nbsp;&nbsp; 
在第3章中，HttpConnector实例一次仅仅拥有一个HttpProcessor实例，所以每次只能处理一个HTTP请求。在默认连接器 
中，HttpConnector拥有一个HttpProcessor对象池，每个HttpProcessor实例拥有一个独立线程。因 
此，HttpConnector可以同时处理多个HTTP请求。<BR>&nbsp;&nbsp;&nbsp; 
HttpConnector维护一个HttpProcessor的实例池，从而避免每次创建HttpProcessor实例。这些HttpProcessor实例是存放在一个叫processors的java.io.Stack中：
<BLOCKQUOTE>private Stack processors = new 
Stack();<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在HttpConnector中，创建的HttpProcessor实例数量是有两个变量决定的：minProcessors和 
maxProcessors。默认情况下，minProcessors为5而maxProcessors为20，但是你可以通过 
setMinProcessors和setMaxProcessors方法来改变他们的值。<BR>
<BLOCKQUOTE>protected int minProcessors = 5;<BR>private int maxProcessors = 
  20;<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
开始的时候，HttpConnector对象创建minProcessors个HttpProcessor实例。如果一次有比HtppProcessor 
实例更多的请求需要处理时，HttpConnector创建更多的HttpProcessor实例，直到实例数量达到maxProcessors个。在到 
达这点之后，仍不够HttpProcessor实例的话，请来的请求将会给忽略掉。如果你想让HttpConnector继续创建 
HttpProcessor实例的话，把maxProcessors设置为一个负数。还有就是变量curProcessors保存了 
HttpProcessor实例的当前数量。<BR>&nbsp;&nbsp;&nbsp; 
下面是类HttpConnector的start方法里边关于创建初始数量的HttpProcessor实例的代码：<BR>
<BLOCKQUOTE>while (curProcessors &lt; minProcessors) {<BR>&nbsp;&nbsp; 
  &nbsp;if ((maxProcessors &gt; 0) &amp;&amp; (curProcessors &gt;= 
  maxProcessors))<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;break;<BR>&nbsp;&nbsp; &nbsp;HttpProcessor processor = 
  newProcessor();<BR>&nbsp;&nbsp; 
&nbsp;recycle(processor);<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
newProcessor方法构造一个HttpProcessor对象并增加curProcessors。recycle方法把HttpProcessor队会栈。<BR>&nbsp;&nbsp;&nbsp; 
每个HttpProcessor实例负责解析HTTP请求行和头部，并填充请求对象。因此，每个实例关联着一个请求对象和响应对象。类 
HttpProcessor的构造方法包括了类HttpConnector的createRequest和createResponse方法的调用。<BR>
<H4>为HTTP请求服务</H4>&nbsp;&nbsp;&nbsp; 
就像第3章一样，HttpConnector类在它的run方法中有其主要的逻辑。run方法在一个服务端套接字等待HTTP请求的地方存在一个while循环，一直运行直至HttpConnector被关闭了。<BR>
<BLOCKQUOTE>while (!stopped) {<BR>&nbsp;&nbsp; &nbsp;Socket socket = 
  null;<BR>&nbsp;&nbsp; &nbsp;try {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;socket = serverSocket.accept();<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;...<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
对每个前来的HTTP请求，会通过调用私有方法createProcessor获得一个HttpProcessor实例。<BR>
<BLOCKQUOTE>HttpProcessor processor = 
createProcessor();<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然而，大部分时候createProcessor方法并不创建一个新的HttpProcessor对象。相反，它从池子中获取一个。如果在栈中已经存在一 
个HttpProcessor实例，createProcessor将弹出一个。如果栈是空的并且没有超过HttpProcessor实例的最大数 
量，createProcessor将会创建一个。然而，如果已经达到最大数量的话，createProcessor将会返回null。出现这样的情况的 
话，套接字将会简单关闭并且前来的HTTP请求不会被处理。<BR>
<BLOCKQUOTE>if (processor == null) {<BR>&nbsp;&nbsp; &nbsp;try 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;log(sm.getString("httpConnector.noProcessor"));<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;socket.close();<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;...<BR>&nbsp;&nbsp; 
&nbsp;continue;<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
如果createProcessor不是返回null，客户端套接字会传递给HttpProcessor类的assign方法：<BR>
<BLOCKQUOTE>processor.assign(socket);<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
现在就是HttpProcessor实例用于读取套接字的输入流和解析HTTP请求的工作了。重要的一点是，assign方法不会等到 
HttpProcessor完成解析工作，而是必须马上返回，以便下一个前来的HTTP请求可以被处理。每个HttpProcessor实例有自己的线程 
用于解析，所以这点不是很难做到。你将会在下节“HttpProcessor类”中看到是怎么做的。<BR>
<H4>HttpProcessor类</H4>&nbsp;&nbsp;&nbsp; 
默认连接器中的HttpProcessor类是第3章中有着类似名字的类的全功能版本。你已经学习了它是如何工作的，在本章中，我们很有兴趣知道 
HttpProcessor类怎样让assign方法异步化，这样HttpProcessor实例就可以同时间为很多HTTP请求服务了。<BR><B>&nbsp;&nbsp; 
&nbsp;注意：</B> 
HttpProcessor类的另一个重要方法是私有方法process，它是用于解析HTTP请求和调用容器的invoke方法的。我们将会在本章稍后部分的“处理请求”一节中看到它。<BR>&nbsp;&nbsp;&nbsp; 
在第3章中，HttpConnector在它自身的线程中运行。但是，在处理下一个请求之前，它必须等待当前处理的HTTP请求结束。下面是第3章中HttpProcessor类的run方法的部分代码：<BR>
<BLOCKQUOTE>public void run() {<BR>&nbsp;&nbsp; &nbsp;...<BR>&nbsp;&nbsp; 
  &nbsp;while (!stopped) {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Socket 
  socket = null;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;socket = 
  serversocket.accept();<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;catch (Exception e) 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;continue;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;// Hand this socket off to an Httpprocessor<BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;HttpProcessor processor = new Httpprocessor(this);</SPAN><BR 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;processor.process(socket);</SPAN><BR>&nbsp;&nbsp; 
&nbsp;}<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
第3章中的HttpProcessor类的process方法是同步的。因此，在接受另一个请求之前，它的run方法要等待process方法运行结束。<BR>&nbsp;&nbsp;&nbsp; 
在默认连接器中，然而，HttpProcessor类实现了java.lang.Runnable并且每个HttpProcessor实例运行在称作处理 
器线程(processor 
thread)的自身线程上。对HttpConnector创建的每个HttpProcessor实例，它的start方法将被调用，有效的启动了 
HttpProcessor实例的处理线程。Listing 4.1展示了默认处理器中的HttpProcessor类的run方法：<BR>Listing 4.1: 
The HttpProcessor class's run method.<BR>
<BLOCKQUOTE>public void run() {<BR>&nbsp;&nbsp; &nbsp;// Process requests 
  until we receive a shutdown signal<BR>&nbsp;&nbsp; &nbsp;while (!stopped) 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;// Wait for the next socket to be 
  assigned<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Socket socket = 
  await();<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (socket == 
  null)<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;continue;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;// Process the 
  request from this socket<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;process(socket);<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;catch (Throwable t) 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;log("process.invoke", t);<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;// Finish up this 
  request<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;connector.recycle(this);<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; 
  &nbsp;// Tell threadStop() we have shut ourselves down 
  successfully<BR>&nbsp;&nbsp; &nbsp;synchronized (threadSync) {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;threadSync.notifyAll();<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
run方法中的while循环按照这样的循序进行：获取一个套接字，处理它，调用连接器的recycle方法把当前的HttpProcessor实例推回栈。这里是HttpConenctor类的recycle方法：<BR>
<BLOCKQUOTE>void recycle(HttpProcessor processor) {<BR>&nbsp;&nbsp; 
  &nbsp;processors.push(processor);<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
需要注意的是，run中的while循环在await方法中结束。await方法持有处理线程的控制流，直到从HttpConnector中获取到一个新的套接字。用另外一种说法就是，直到HttpConnector调用HttpProcessor实例的assign方法。但是，await方法和assign方 
法运行在不同的线程上。assign方法从HttpConnector的run方法中调用。我们就说这个线程是HttpConnector实例的run方法运行的处理线程。assign方法是如何通知已经被调用的await方法的？就是通过一个布尔变量available并且使用java.lang.Object的wait和notifyAll方法。<BR>&nbsp;&nbsp;&nbsp; 
<B>注意：</B>wait方法让当前线程等待直到另一个线程为这个对象调用notify或者notifyAll方法为止。<BR>这里是HttpProcessor类的assign和await方法：<BR>
<BLOCKQUOTE>synchronized void assign(Socket socket) {<BR>&nbsp;&nbsp; &nbsp;// 
  Wait for the processor to get the previous socket<BR>&nbsp;&nbsp; &nbsp;while 
  (available) {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;wait();<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;catch 
  (InterruptedException e) {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;// Store the newly 
  available Socket and notify our thread<BR>&nbsp;&nbsp; &nbsp;this.socket = 
  socket;<BR>&nbsp;&nbsp; &nbsp;available = true;<BR>&nbsp;&nbsp; 
  &nbsp;notifyAll();<BR>&nbsp;&nbsp; &nbsp;...<BR>}<BR>private synchronized 
  Socket await() {<BR>&nbsp;&nbsp; &nbsp;// Wait for the Connector to provide a 
  new Socket<BR>&nbsp;&nbsp; &nbsp;while (!available) {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;try {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;wait();<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;catch (InterruptedException 
  e) {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;// Notify the Connector that we have received 
  this Socket<BR>&nbsp;&nbsp; &nbsp;Socket socket = this.socket;<BR>&nbsp;&nbsp; 
  &nbsp;available = false;<BR>&nbsp;&nbsp; &nbsp;notifyAll();<BR>&nbsp;&nbsp; 
  &nbsp;if ((debug &gt;= 1) &amp;&amp; (socket != null))<BR>&nbsp;&nbsp; 
  &nbsp;log(" The incoming request has been awaited");<BR>&nbsp;&nbsp; 
  &nbsp;return (socket);<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 两个方法的程序流向在Table 
4.1中总结。<BR>Table 4.1: Summary of the await and assign method<BR>
<BLOCKQUOTE>The processor thread (the await method) The connector thread (the 
  assign method)<BR>while (!available) 
  {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  while (available) 
  {<BR>wait();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  wait();<BR>}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  }<BR>Socket socket = 
  this.socket;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  this.socket = socket;<BR>available = 
  false;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  available = 
  true;<BR>notifyAll();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  notifyAll();<BR>return socket; // to the 
  run&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
  ...<BR>// method<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
刚开始的时候，当处理器线程刚启动的时候，available为false，线程在while循环里边等待(见Table 
4.1的第1列)。它将等待另一个线程调用notify或notifyAll。这就是说，调用wait方法让处理器线程暂停，直到连接器线程调用HttpProcessor实例的notifyAll方法。<BR>&nbsp;&nbsp;&nbsp; 
现在，看看第2列，当一个新的套接字被分配的时候，连接器线程调用HttpProcessor的assign方法。available的值是false，所以while循环给跳过，并且套接字给赋值给HttpProcessor实例的socket变量：<BR>
<BLOCKQUOTE>this.socket = socket;<BR></BLOCKQUOTE>&nbsp;&nbsp; 
&nbsp;连接器线程把available设置为true并调用notifyAll。这就唤醒了处理器线程，因为available为true，所以程序控制跳出while循环：把实例的socket赋值给一个本地变量，并把available设置为false，调用notifyAll，返回最后需要进行处理的socket。<BR>&nbsp;&nbsp;&nbsp; 
为什么await需要使用一个本地变量(socket)而不是返回实例的socket变量呢？因为这样一来，在当前socket被完全处理之前，实例的socket变量可以赋给下一个前来的socket。<BR>&nbsp;&nbsp;&nbsp; 
为什么await方法需要调用notifyAll呢? 
这是为了防止在available为true的时候另一个socket到来。在这种情况下，连接器线程将会在assign方法的while循环中停止，直到接收到处理器线程的notifyAll调用。<BR>
<H4>请求对象</H4>&nbsp;&nbsp;&nbsp; 
默认连接器哩变得HTTP请求对象指代org.apache.catalina.Request接口。这个接口被类RequestBase直接实现了，也是HttpRequest的父接口。最终的实现是继承于HttpRequest的HttpRequestImpl。像第3章一样，有几个facade类：RequestFacade和HttpRequestFacade。Request接口和它的实现类的UML图在Figure 
4.2中给出。注意的是，除了属于javax.servlet和javax.servlet.http包的类，前缀org.apache.catalina已经被省略了。
<DIV id=h7al style="TEXT-ALIGN: left"><IMG style="WIDTH: 551px; HEIGHT: 284px" 
src="HowTomcatWorks中文版.files/dc32cxpz_39cdhnhgfk_b"></DIV>Figure 4.2: The 
Request interface and related types<BR>&nbsp;&nbsp;&nbsp; 
如果你理解第3章的请求对象，理解这个结构图你应该不会遇到什么困难。<BR>
<H4>响应对象</H4>&nbsp;&nbsp;&nbsp; Response接口和它的实现类的UML图在Figure 4.3中给出。
<DIV id=s7gj style="TEXT-ALIGN: left"><IMG style="WIDTH: 548px; HEIGHT: 300px" 
src="HowTomcatWorks中文版.files/dc32cxpz_40cqk2zhgz_b"></DIV>&nbsp;&nbsp; 
&nbsp;Figure 4.3: The Response interface and its implementation classes<BR>
<H4>处理请求</H4>&nbsp;&nbsp;&nbsp; 
到这个时候，你已经理解了请求和响应对象，并且知道HttpConnector对象是如何创建它们的。现在是这个过程的最后一点东西了。在这节中我们关注HttpProcessor类的process方法，它是一个套接字赋给它之后，在HttpProcessor类的run方法中调用的。process方法会做下面这些工作：<BR>
<UL>
  <LI>解析连接
  <LI>解析请求
  <LI>解析头部</LI></UL>&nbsp;&nbsp;&nbsp; 
在解释完process方法之后，在本节的各个小节中将讨论每个操作。<BR>&nbsp;&nbsp;&nbsp; 
process方法使用布尔变量ok来指代在处理过程中是否发现错误，并使用布尔变量finishResponse来指代Response接口中的finishResponse方法是否应该被调用。<BR>
<BLOCKQUOTE>boolean ok = true;<BR>boolean finishResponse = 
true;<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
另外，process方法也使用了布尔变量keepAlive,stopped和http11。keepAlive表示连接是否是持久的，stopped表示HttpProcessor实例是否已经被连接器终止来确认process是否也应该停止，http11表示 
从web客户端过来的HTTP请求是否支持HTTP 1.1。<BR>&nbsp;&nbsp;&nbsp; 
像第3章那样，有一个SocketInputStream实例用来包装套接字的输入流。注意的是，SocketInputStream的构造方法同样传递了从连接器获得的缓冲区大小，而不是从HttpProcessor的本地变量获得。这是因为对于默认连接器的用户而言，HttpProcessor是不可访问的。通过传递Connector接口的缓冲区大小，这就使得使用连接器的任何人都可以设置缓冲大小。<BR>
<BLOCKQUOTE>SocketInputStream input = null;<BR>OutputStream output = 
  null;<BR>// Construct and initialize the objects we will need<BR>try 
  {<BR>&nbsp;&nbsp; &nbsp;input = new 
  SocketInputStream(socket.getInputstream(),<BR>&nbsp;&nbsp; 
  &nbsp;connector.getBufferSize());<BR>}<BR>catch (Exception e) 
  {<BR>&nbsp;&nbsp; &nbsp;ok = false;<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
然后，有个while循环用来保持从输入流中读取，直到HttpProcessor被停止，一个异常被抛出或者连接给关闭为止。<BR>
<BLOCKQUOTE>keepAlive = true;<BR>while (!stopped &amp;&amp; ok &amp;&amp; 
  keepAlive) {<BR>&nbsp;&nbsp; &nbsp;...<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在while循环的内部，process方法首先把finishResponse设置为true，并获得输出流，并对请求和响应对象做些初始化处理。<BR>
<BLOCKQUOTE>finishResponse = true;<BR>try {<BR>&nbsp;&nbsp; 
  &nbsp;request.setStream(input);<BR>&nbsp;&nbsp; 
  &nbsp;request.setResponse(response);<BR>&nbsp;&nbsp; &nbsp;output = 
  socket.getOutputStream();<BR>&nbsp;&nbsp; 
  &nbsp;response.setStream(output);<BR>&nbsp;&nbsp; 
  &nbsp;response.setRequest(request);<BR>&nbsp;&nbsp; 
  &nbsp;((HttpServletResponse) response.getResponse()).setHeader("Server", 
  SERVER_INFO);<BR>}<BR>catch (Exception e) {<BR>&nbsp;&nbsp; 
  &nbsp;log("process.create", e); //logging is discussed in Chapter 
  7<BR>&nbsp;&nbsp; &nbsp;ok = false;<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接着，process方法通过调用parseConnection，parseRequest和parseHeaders方法开始解析前来的HTTP请求，这些方法将在这节的小节中讨论。<BR>
<BLOCKQUOTE>try {<BR>&nbsp;&nbsp; &nbsp;if (ok) {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;parseConnection(socket);<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;parseRequest(input, output);<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;if 
  (!request.getRequest().getProtocol().startsWith("HTTP/0"))<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;parseHeaders(input);<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
parseConnection方法获得协议的值，像HTTP0.9, 
HTTP1.0或HTTP1.1。如果协议是HTTP1.0，keepAlive设置为false，因为HTTP1.0不支持持久连接。如果在HTTP请求里边找到Expect: 
100-continue的头部信息，则parseHeaders方法将把sendAck设置为true。<BR>&nbsp;&nbsp;&nbsp; 
如果协议是HTTP1.1，并且web客户端发送头部Expect: 
100-continue的话，通过调用ackRequest方法它将响应这个头部。它将会测试组块是否是允许的。<BR>
<BLOCKQUOTE>if (http11) {<BR>&nbsp;&nbsp; &nbsp;// Sending a request 
  acknowledge back to the client if requested.<BR>&nbsp;&nbsp; 
  &nbsp;ackRequest(output);<BR>&nbsp;&nbsp; &nbsp;// If the protocol is 
  HTTP/1.1, chunking is allowed.<BR>&nbsp;&nbsp; &nbsp;if 
  (connector.isChunkingAllowed())<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;response.setAllowChunking(true);<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
ackRequest方法测试sendAck的值，并在sendAck为true的时候发送下面的字符串：<BR>
<BLOCKQUOTE>HTTP/1.1 100 Continue\r\n\r\n<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在解析HTTP请求的过程中，有可能会抛出异常。任何异常将会把ok或者finishResponse设置为false。在解析过后，process方法把请求和响应对象传递给容器的invoke方法：<BR>
<BLOCKQUOTE>try {<BR>&nbsp;&nbsp; &nbsp;((HttpServletResponse) 
  response).setHeader("Date", 
  FastHttpDateFormat.getCurrentDate());<BR>&nbsp;&nbsp; &nbsp;if (ok) 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;connector.getContainer().invoke(request, response);<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接着，如果finishResponse仍然是true，响应对象的finishResponse方法和请求对象的finishRequest方法将被调用，并且结束输出。<BR>
<BLOCKQUOTE>if (finishResponse) {<BR>&nbsp;&nbsp; &nbsp;...<BR>&nbsp;&nbsp; 
  &nbsp;response.finishResponse();<BR>&nbsp;&nbsp; &nbsp;...<BR>&nbsp;&nbsp; 
  &nbsp;request.finishRequest();<BR>&nbsp;&nbsp; &nbsp;...<BR>&nbsp;&nbsp; 
  &nbsp;output.flush();<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
while循环的最后一部分检查响应的Connection头部是否已经在servlet内部设为close，或者协议是HTTP1.0.如果是这种情况的话，keepAlive设置为false。同样，请求和响应对象接着会被回收利用。<BR>
<BLOCKQUOTE>if ( "close".equals(response.getHeader("Connection")) ) 
  {<BR>&nbsp;&nbsp; &nbsp;keepAlive = false;<BR>}<BR>// End of request 
  processing<BR>status = Constants.PROCESSOR_IDLE;<BR>// Recycling the request 
  and the response 
objects<BR>request.recycle();<BR>response.recycle();<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
在这个场景中，如果哦keepAlive是true的话，while循环将会在开头就启动。因为在前面的解析过程中和容器的invoke方法中没有出现错误，或者HttpProcessor实例没有被停止。否则，shutdownInput方法将会调用，而套接字将被关闭。<BR>
<BLOCKQUOTE>try {<BR>&nbsp;&nbsp; &nbsp;shutdownInput(input);<BR>&nbsp;&nbsp; 
  &nbsp;socket.close();<BR>}<BR>...<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
shutdownInput方法检查是否有未读取的字节。如果有的话，跳过那些字节。<BR>
<H4>解析连接</H4>&nbsp;&nbsp;&nbsp; 
parseConnection方法从套接字中获取到网络地址并把它赋予HttpRequestImpl对象。它也检查是否使用代理并把套接字赋予请求对象。parseConnection方法在Listing4.2中列出。<BR>Listing 
4.2: The parseConnection method<BR>
<BLOCKQUOTE>private void parseConnection(Socket socket) throws IOException, 
  ServletException {<BR>&nbsp;&nbsp; &nbsp;if (debug &gt;= 2)<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;log(" parseConnection: address=" + 
  socket.getInetAddress() +<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;", port=" + connector.getPort());<BR>&nbsp;&nbsp; 
  &nbsp;((HttpRequestImpl) 
  request).setInet(socket.getInetAddress());<BR>&nbsp;&nbsp; &nbsp;if (proxyPort 
  != 0)<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;request.setServerPort(proxyPort);<BR>&nbsp;&nbsp; 
  &nbsp;else<BR>&nbsp;&nbsp; 
  &nbsp;request.setServerPort(serverPort);<BR>&nbsp;&nbsp; 
  &nbsp;request.setSocket(socket);<BR>}<BR></BLOCKQUOTE>
<H4>解析请求</H4>&nbsp;&nbsp;&nbsp; 
parseRequest方法是第3章中类似方法的完整版本。如果你很好的理解第3章的话，你通过阅读这个方法应该可以理解这个方法是怎么运行的。<BR>
<H4>解析头部</H4>&nbsp;&nbsp;&nbsp; 
默认链接器的parseHeaders方法使用包org.apache.catalina.connector.http里边的HttpHeader和DefaultHeaders类。类HttpHeader指代一个HTTP请求头部。类HttpHeader不是像第3章那样使用字符串，而是使用字符数据用来避免昂贵的字符串操作。类DefaultHeaders是一个final类，在字符数组中包含了标准的HTTP请求头部：<BR>
<BLOCKQUOTE>standard HTTP request headers in character arrays:<BR>static final 
  char[] AUTHORIZATION_NAME = "authorization".toCharArray();<BR>static final 
  char[] ACCEPT_LANGUAGE_NAME = "accept-language".toCharArray();<BR>static final 
  char[] COOKIE_NAME = 
"cookie".toCharArray();<BR>...<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
parseHeaders方法包含一个while循环，可以持续读取HTTP请求直到再也没有更多的头部可以读取到。while循环首先调用请求对象的allocateHeader方法来获取一个空的HttpHead实例。这个实例被传递给<BR>SocketInputStream的readHeader方法。<BR>
<BLOCKQUOTE>HttpHeader header = request.allocateHeader();<BR>// Read the next 
  header<BR>input.readHeader(header);<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
假如所有的头部都被已经被读取的话，readHeader方法将不会赋值给HttpHeader实例，这个时候parseHeaders方法将会返回。<BR>
<BLOCKQUOTE>if (header.nameEnd == 0) {<BR>&nbsp;&nbsp; &nbsp;if 
  (header.valueEnd == 0) {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;return;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;else 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;throw new &nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; 
  &nbsp;ServletException(sm.getString("httpProcessor.parseHeaders.colon"));<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
如果存在一个头部的名称的话，这里必须同样会有一个头部的值：<BR>
<BLOCKQUOTE>String value = new String(header.value, 0, 
header.valueEnd);<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
接下去，像第3章那样，parseHeaders方法将会把头部名称和DefaultHeaders里边的名称做对比。注意的是，这样的对比是基于两个字符数组之间，而不是两个字符串之间的。<BR>
<BLOCKQUOTE>if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) 
  {<BR>&nbsp;&nbsp; &nbsp;request.setAuthorization(value);<BR>}<BR>else if 
  (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {<BR>&nbsp;&nbsp; 
  &nbsp;parseAcceptLanguage(value);<BR>}<BR>else if 
  (header.equals(DefaultHeaders.COOKIE_NAME)) {<BR>&nbsp;&nbsp; &nbsp;// parse 
  cookie<BR>}<BR>else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) 
  {<BR>&nbsp;&nbsp; &nbsp;// get content length<BR>}<BR>else if 
  (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {<BR>&nbsp;&nbsp; 
  &nbsp;request.setContentType(value);<BR>}<BR>else if 
  (header.equals(DefaultHeaders.HOST_NAME)) {<BR>&nbsp;&nbsp; &nbsp;// get host 
  name<BR>}<BR>else if (header.equals(DefaultHeaders.CONNECTION_NAME)) 
  {<BR>&nbsp;&nbsp; &nbsp;if 
  (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;keepAlive = false;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;response.setHeader("Connection", "close");<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>}<BR>else if (header.equals(DefaultHeaders.EXPECT_NAME)) 
  {<BR>&nbsp;&nbsp; &nbsp;if 
  (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;sendAck = true;<BR>&nbsp;&nbsp; 
  &nbsp;else<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;throw new 
  ServletException(sm.getstring<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; 
  &nbsp;("httpProcessor.parseHeaders.unknownExpectation"));<BR>}<BR>else if 
  (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {<BR>&nbsp;&nbsp; 
  &nbsp;//request.setTransferEncoding(header);<BR>}<BR>request.nextHeader();<BR></BLOCKQUOTE>
<H4>简单容器的应用程序</H4>&nbsp;&nbsp;&nbsp; 
本章的应用程序的主要目的是展示默认连接器是怎样工作的。它包括两个类：<BR>&nbsp;&nbsp; 
&nbsp;ex04.pyrmont.core.SimpleContainer和ex04 
pyrmont.startup.Bootstrap。类<BR>SimpleContainer实现了org.apache.catalina.container接口，所以它可以和连接器关联。类Bootstrap是用来启动应用程序的，我们已经移除了第3章带的应用程序中的连接器模块，类ServletProcessor和<BR>StaticResourceProcessor，所以你不能请求一个静态页面。<BR>&nbsp;&nbsp;&nbsp; 
类SimpleContainer展示在Listing 4.3.<BR>Listing 4.3: The SimpleContainer class<BR>
<BLOCKQUOTE>package ex04.pyrmont.core;<BR>import 
  java.beans.PropertyChangeListener;<BR>import java.net.URL;<BR>import 
  java.net.URLClassLoader;<BR>import java.net.URLStreamHandler;<BR>import 
  java.io.File;<BR>import java.io.IOException;<BR>import 
  javax.naming.directory.DirContext;<BR>import javax.servlet.Servlet;<BR>import 
  javax.servlet.ServletException;<BR>import 
  javax.servlet.http.HttpServletRequest;<BR>import 
  javax.servlet.http.HttpServletResponse;<BR>import 
  org.apache.catalina.Cluster;<BR>import 
  org.apache.catalina.Container;<BR>import 
  org.apache.catalina.ContainerListener;<BR>import 
  org.apache.catalina.Loader;<BR>import org.apache.catalina.Logger;<BR>import 
  org.apache.catalina.Manager;<BR>import org.apache.catalina.Mapper;<BR>import 
  org.apache.catalina.Realm;<BR>import org.apache.catalina.Request;<BR>import 
  org.apache.catalina.Response;<BR>public class SimpleContainer implements 
  Container {<BR>&nbsp;&nbsp; &nbsp;public static final String WEB_ROOT 
  =<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;System.getProperty("user.dir") + 
  File.separator + "webroot";<BR>&nbsp;&nbsp; &nbsp;public SimpleContainer() { 
  }<BR>&nbsp;&nbsp; &nbsp;public String getInfo() {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;return null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; 
  &nbsp;public Loader getLoader() {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;return null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;public void 
  setLoader(Loader loader) { }<BR>&nbsp;&nbsp; &nbsp;public Logger getLogger() 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return null;<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;public void setLogger(Logger logger) { 
  }<BR>&nbsp;&nbsp; &nbsp;public Manager getManager() {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;return null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; 
  &nbsp;public void setManager(Manager manager) { }<BR>&nbsp;&nbsp; &nbsp;public 
  Cluster getCluster() {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return 
  null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;public void 
  setCluster(Cluster cluster) { }<BR>&nbsp;&nbsp; &nbsp;public String getName() 
  {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return null;<BR>&nbsp;&nbsp; 
  &nbsp;}<BR>&nbsp;&nbsp; &nbsp;public void setName(String name) { 
  }<BR>&nbsp;&nbsp; &nbsp;public Container getParent() {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;return null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; 
  &nbsp;public void setParent(Container container) { }<BR>&nbsp;&nbsp; 
  &nbsp;public ClassLoader getParentClassLoader() {<BR>&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp; &nbsp;return null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; 
  &nbsp;public void setParentClassLoader(ClassLoader parent) { }<BR>&nbsp;&nbsp; 
  &nbsp;public Realm getRealm() {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
  &nbsp;return null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;public void 
  setRealm(Realm realm) { }<BR>&nbsp;&nbsp; &nbsp;public DirContext 
  getResources() {<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return 
  null;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;public void 
  setResources(DirContext resources) { }<BR>&nbsp;&nbsp;&nbsp; public void 
  addChild(Container child) { }<BR>&nbsp;&nbsp; &nbsp;public void 
  addContainerListener(ContainerListener listener) { }<BR>&nbsp;&nbsp; 
  &nbsp;public void addMapper(Mapper mapper) { }<BR>&nbsp;&nbsp; &nbsp;public 
  void addPropertyChangeListener(<BR>PropertyChangeListener listener) { 
  }<BR>public Container findchild(String name) {<BR>return null;<BR>}<BR>public 
  Container[] findChildren() {<BR>return null;<BR>}<BR>public 
  ContainerListener[] findContainerListeners() {<BR>return null;<BR>}<BR>public 
  Mapper findMapper(String protocol) {<BR>return null;<BR>}<BR>public Mapper[] 
  findMappers() {<BR>return null;<BR>}<BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">public void invoke(Request request, 
  Response response)</SPAN><BR style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">throws IoException, 
  ServletException {</SPAN><BR style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">string servletName = ( 
  (Httpservletrequest)</SPAN><BR 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">request).getRequestURI();</SPAN><BR 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">servletName = 
  servletName.substring(servletName.lastIndexof("/") +</SPAN><BR 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">1);</SPAN><BR 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">URLClassLoader loader = 
  null;</SPAN><BR style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">try {</SPAN><BR 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">URL[] urls = new URL[1];</SPAN><BR 
  style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">URLStreamHandler streamHandler = 
  null;</SPAN><BR style="BACKGROUND-COLOR: rgb(204,204,204)"><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">File classpath = new 
  File(WEB_ROOT);</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">string repository = (new 
  URL("file",null,</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">classpath.getCanonicalpath() + 
  File.separator)).toString();</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">urls[0] = new URL(null, repository, 
  streamHandler);</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">loader = new 
  URLClassLoader(urls);</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">}</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">catch (IOException e) 
  {</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">System.out.println(e.toString() 
  );</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">}</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">Class myClass = 
  null;</SPAN><BR><SPAN style="BACKGROUND-COLOR: rgb(204,204,204)">try 
  {</SPAN><BR><SPAN style="BACKGROUND-COLOR: rgb(204,204,204)">myClass = 
  loader.loadclass(servletName);</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">}</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">catch (classNotFoundException e) 
  {</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">System.out.println(e.toString());</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">}</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">servlet servlet = 
  null;</SPAN><BR><SPAN style="BACKGROUND-COLOR: rgb(204,204,204)">try 
  {</SPAN><BR><SPAN style="BACKGROUND-COLOR: rgb(204,204,204)">servlet = 
  (Servlet) myClass.newInstance();</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">servlet.service((HttpServletRequest) 
  request,</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">(HttpServletResponse) 
  response);</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">}</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">catch (Exception e) 
  {</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">System.out.println(e.toString());</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">}</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">catch (Throwable e) 
  {</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">System.out.println(e.toString());</SPAN><BR><SPAN 
  style="BACKGROUND-COLOR: rgb(204,204,204)">}</SPAN><BR>}<BR>public Container 
  map(Request request, boolean update) {<BR>return null;<BR>}<BR>public void 
  removeChild(Container child) { }<BR>public void 
  removeContainerListener(ContainerListener listener) { }<BR>public void 
  removeMapper(Mapper mapper) { }<BR>public void 
  removoPropertyChangeListener(<BR>PropertyChangeListener listener) 
  {<BR>}<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
我只是提供了SimpleContainer类的invoke方法的实现，因为默认连接器将会调用这个方法。invoke方法创建了一个类加载器，加载servlet类，并调用它的service方法。这个方法和第3章的ServletProcessor类在哦个的process方法非常类似。<BR>&nbsp;&nbsp;&nbsp; 
Bootstrap类在Listing 4.4在列出.<BR>&nbsp;&nbsp; &nbsp;Listing 4.4: The 
ex04.pyrmont.startup.Bootstrap class<BR>
<BLOCKQUOTE>package ex04.pyrmont.startup;<BR>import 
  ex04.pyrmont.core.simplecontainer;<BR>import 
  org.apache.catalina.connector.http.HttpConnector;<BR>public final class 
  Bootstrap {<BR>public static void main(string[] args) {<BR>HttpConnector 
  connector = new HttpConnector();<BR>SimpleContainer container = new 
  SimpleContainer();<BR>connector.setContainer(container);<BR>try 
  {<BR>connector.initialize();<BR>connector.start();<BR>// make the application 
  wait until we press any key.<BR>System in.read();<BR>}<BR>catch (Exception e) 
  {<BR>e.printStackTrace();<BR>}<BR>}<BR>}<BR></BLOCKQUOTE>&nbsp;&nbsp;&nbsp; 
Bootstrap 类的main方法构造了一个org.apache.catalina.connector.http.HttpConnector实例和一个 
SimpleContainer实例。它接下去调用conncetor的setContainer方法传递container，让connector和container关联起来。下一步，它调用connector的initialize和start方法。这将会使得connector为处理8080端口上的任何请求做好了准备。<BR>&nbsp;&nbsp;&nbsp; 
你可以通过在控制台中输入一个按键来终止这个应用程序。<BR>
<H3>运行应用程序</H3>
<P>&nbsp;&nbsp;&nbsp; 要在Windows中运行这个程序的话，在工作目录下输入以下内容：</P>java -classpath 
./lib/servlet.jar;./ ex04.pyrmont.startup.Bootstrap<BR>&nbsp;&nbsp;&nbsp; 
在Linux的话，你可以使用分号来分隔两个库。<BR>java -classpath ./lib/servlet.jar:./ 
ex04.pyrmont.startup.Bootstrap<BR>&nbsp;&nbsp;&nbsp; 
你可以和第三章那样调用PrimitiveServlet和ModernServlet。<BR>&nbsp;&nbsp;&nbsp; 
注意的是你不能请求index.html，因为没有静态资源的处理器。<BR>
<H3>总结</H3>&nbsp;&nbsp;&nbsp; 
本章展示了如何构建一个能和Catalina工作的Tomcat连接器。剖析了Tomcat4的默认连接器的代码并用这个连接器构建了一个小应用程序。接下来的章节的所有应用程序都会使用默认连接器。<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>------------32<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR 
id=v5vw20><BR id=fkp729><BR id=z43.3><BR id=n7.88><BR id=qpcz39><BR 
id=h7737><BR></DIV>
<DIV id=tkxc9> </DIV>
<DIV id=tkxc10> </DIV>
<DIV id=tkxc11> </DIV>
<DIV id=tkxc12> </DIV>
<DIV id=tkxc13> </DIV><BR id=d5qz><BR clear=all></DIV>
<DIV id=google-view-footer>
<DIV id=maybecanedit style="FLOAT: right"><A class=google-small-link 
id=editpermissionlink title=编辑此页 
href="http://docs.google.com/Doc?tab=edit&amp;dr=true&amp;id=dc32cxpz_31ghgkhqf3">编辑此页面（如果您有权限）</A> 
<SPAN style="COLOR: #676767">|</SPAN> <INPUT id=report-abuse-button onclick=reportAbuse(); type=button value=报告滥用行为></DIV>
<DIV style="FLOAT: left"><A class=google-small-link title="了解“Google 文档”的详细信息" 
href="http://docs.google.com/">Google 文档 － 网络文字处理、演示文稿和电子表格。</A> </DIV>
<P> </P></DIV>
<SCRIPT><!--
    viewOnLoad();
    if(window.jstiming){window.jstiming.a={};window.jstiming.c=1;var j=function(a,b,e){var c=a.t[b],g=a.t.start;if(c&&(g||e)){c=a.t[b][0];g=e!=undefined?e:g[0];return c-g}};window.jstiming.report=function(a,b,e){var c="";if(window.jstiming.pt){c+="&srt="+window.jstiming.pt;delete window.jstiming.pt}try{if(window.external&&window.external.tran)c+="&tran="+window.external.tran;else if(window.gtbExternal&&window.gtbExternal.tran)c+="&tran="+window.gtbExternal.tran()}catch(g){}if(a.b)c+="&"+a.b;var f=a.t,
n=f.start,k=[],h=[];for(var d in f)if(d!="start")if(d.indexOf("_")!=0){var i=f[d][1];if(i)f[i]&&h.push(d+"."+j(a,d,f[i][0]));else n&&k.push(d+"."+j(a,d))}delete f.start;if(b)for(var l in b)c+="&"+l+"="+b[l];a=[e?e:"http://csi.gstatic.com/csi","?v=3","&s="+(window.jstiming.sn||"writely")+"&action=",a.name,h.length?"&it="+h.join(","):"",c,"&rt=",k.join(",")].join("");b=new Image;var m=window.jstiming.c++;window.jstiming.a[m]=b;b.onload=b.onerror=function(){delete window.jstiming.a[m]};b.src=a;b=null;
return a}};

    window.jstiming.load.name = 'published';
    
    
    var urchinPage = "/View";

    
    function getXHR() {
      if (typeof XMLHttpRequest != "undefined") {
        return new XMLHttpRequest();
      }
      try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
      try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
      try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
      try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
      return null;
    }

    function reportAbuse() {
      var req = getXHR();
      if (req) {
        
          var docid = 'dc32cxpz_31ghgkhqf3';
          var posttoken = '';
        
        req.onreadystatechange = function() {
          try {
            if (req.readyState == 4 && req.status == 200) {
              var button = document.getElementById("report-abuse-button");
              button.value = '谢谢您！';
              button.disabled = true;
            }
          } catch (ex) {
            
          }
        }
        try {
          req.open('POST', 'MiscCommands', true);
          req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
          req.send('command=report_abuse&abuseDoc=' + encodeURIComponent(docid) +
                   '&POST_TOKEN=' + encodeURIComponent(posttoken));
        } catch (ex) {
          
        }
      }
    }
  --></SCRIPT>
</BODY></HTML>
